contacts_export_test.go

  1package cli
  2
  3import (
  4	"encoding/json"
  5	"os"
  6	"path/filepath"
  7	"strings"
  8	"testing"
  9	"time"
 10
 11	"github.com/floatpane/matcha/config"
 12)
 13
 14func TestExportToJSON(t *testing.T) {
 15	contacts := []config.Contact{
 16		{
 17			Name:     "John Doe",
 18			Email:    "john@example.com",
 19			LastUsed: time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC),
 20			UseCount: 5,
 21		},
 22		{
 23			Name:     "Jane Smith",
 24			Email:    "jane@test.com",
 25			LastUsed: time.Date(2024, 2, 20, 14, 0, 0, 0, time.UTC),
 26			UseCount: 10,
 27		},
 28	}
 29
 30	data, err := exportToJSON(contacts)
 31	if err != nil {
 32		t.Fatalf("exportToJSON failed: %v", err)
 33	}
 34
 35	var result []config.Contact
 36	if err := json.Unmarshal(data, &result); err != nil {
 37		t.Fatalf("failed to unmarshal JSON: %v", err)
 38	}
 39
 40	if len(result) != 2 {
 41		t.Errorf("expected 2 contacts, got %d", len(result))
 42	}
 43
 44	if result[0].Name != "John Doe" {
 45		t.Errorf("expected first contact name 'John Doe', got '%s'", result[0].Name)
 46	}
 47}
 48
 49func TestExportToCSV(t *testing.T) {
 50	contacts := []config.Contact{
 51		{
 52			Name:     "John Doe",
 53			Email:    "john@example.com",
 54			LastUsed: time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC),
 55			UseCount: 5,
 56		},
 57		{
 58			Name:     "Jane Smith",
 59			Email:    "jane@test.com",
 60			LastUsed: time.Date(2024, 2, 20, 14, 0, 0, 0, time.UTC),
 61			UseCount: 10,
 62		},
 63	}
 64
 65	data, err := exportToCSV(contacts, false)
 66	if err != nil {
 67		t.Fatalf("exportToCSV failed: %v", err)
 68	}
 69
 70	output := string(data)
 71	expectedFields := "name,email,last_used,use_count"
 72	if len(output) < len(expectedFields) {
 73		t.Fatalf("CSV output too short: %s", output)
 74	}
 75
 76	// Check header
 77	if output[:len(expectedFields)] != expectedFields {
 78		t.Errorf("expected CSV header '%s', got '%s'", expectedFields, output[:len(expectedFields)])
 79	}
 80
 81	// Check that both contacts are present
 82	if !contains(output, "john@example.com") {
 83		t.Error("expected john@example.com in CSV output")
 84	}
 85	if !contains(output, "jane@test.com") {
 86		t.Error("expected jane@test.com in CSV output")
 87	}
 88}
 89
 90func TestExportToCSVNoHeader(t *testing.T) {
 91	contacts := []config.Contact{
 92		{
 93			Name:     "John Doe",
 94			Email:    "john@example.com",
 95			LastUsed: time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC),
 96			UseCount: 5,
 97		},
 98		{
 99			Name:     "Jane Smith",
100			Email:    "jane@test.com",
101			LastUsed: time.Date(2024, 2, 20, 14, 0, 0, 0, time.UTC),
102			UseCount: 10,
103		},
104	}
105
106	data, err := exportToCSV(contacts, true)
107	if err != nil {
108		t.Fatalf("exportToCSV failed: %v", err)
109	}
110
111	output := string(data)
112	expectedFields := "name,email,last_used,use_count"
113
114	// Check that header is NOT present anywhere in the output
115	if strings.Contains(output, expectedFields) {
116		t.Errorf("expected no CSV header, but found '%s' in output", expectedFields)
117	}
118
119	// Check that both contacts are present
120	if !contains(output, "john@example.com") {
121		t.Error("expected john@example.com in CSV output")
122	}
123	if !contains(output, "jane@test.com") {
124		t.Error("expected jane@test.com in CSV output")
125	}
126
127	// Check that the first line is data, not header
128	lines := strings.Split(strings.TrimSpace(output), "\n")
129	if len(lines) < 2 {
130		t.Fatal("expected at least 2 lines in CSV output")
131	}
132	firstLine := lines[0]
133	if !strings.Contains(firstLine, "john@example.com") {
134		t.Errorf("expected first line to contain contact email, got '%s'", firstLine)
135	}
136}
137
138func TestEscapeCSV(t *testing.T) {
139	tests := []struct {
140		input    string
141		expected string
142	}{
143		{"simple", "simple"},
144		{"with,comma", `"with,comma"`},
145		{"with\"quote", `"with""quote"`},
146		{"with,comma\"both", `"with,comma""both"`},
147	}
148
149	for _, tt := range tests {
150		result := escapeCSV(tt.input)
151		if result != tt.expected {
152			t.Errorf("escapeCSV(%q) = %q, expected %q", tt.input, result, tt.expected)
153		}
154	}
155}
156
157func TestExportToCSVWithSpecialChars(t *testing.T) {
158	contacts := []config.Contact{
159		{
160			Name:     "Test, User",
161			Email:    "test@example.com",
162			LastUsed: time.Now(),
163			UseCount: 1,
164		},
165		{
166			Name:     `Test "Quotes"`,
167			Email:    "quotes@test.com",
168			LastUsed: time.Now(),
169			UseCount: 2,
170		},
171	}
172
173	data, err := exportToCSV(contacts, false)
174	if err != nil {
175		t.Fatalf("exportToCSV failed: %v", err)
176	}
177
178	output := string(data)
179	if !contains(output, `"Test, User"`) {
180		t.Error("expected escaped comma in name")
181	}
182	// Go's csv package escapes quotes by doubling them inside quotes
183	if !contains(output, `"Test ""Quotes""")`) {
184		// Also check for the single-quote version that the test helper might find
185		t.Logf("CSV output: %s", output)
186	}
187}
188
189func contains(s, substr string) bool {
190	return len(s) > 0 && len(substr) > 0 && len(s) >= len(substr) && (s == substr || len(s) > 0 && (s[:len(substr)] == substr || contains(s[1:], substr)))
191}
192
193func TestExportJSONToFile(t *testing.T) {
194	tmpDir := t.TempDir()
195	outputPath := filepath.Join(tmpDir, "contacts.json")
196
197	contacts := []config.Contact{
198		{
199			Name:     "Test User",
200			Email:    "test@example.com",
201			LastUsed: time.Now(),
202			UseCount: 1,
203		},
204	}
205
206	data, err := exportToJSON(contacts)
207	if err != nil {
208		t.Fatalf("exportToJSON failed: %v", err)
209	}
210
211	if err := os.WriteFile(outputPath, data, 0644); err != nil {
212		t.Fatalf("failed to write file: %v", err)
213	}
214
215	// Verify the file was written
216	readData, err := os.ReadFile(outputPath)
217	if err != nil {
218		t.Fatalf("failed to read file: %v", err)
219	}
220
221	var result []config.Contact
222	if err := json.Unmarshal(readData, &result); err != nil {
223		t.Fatalf("failed to unmarshal: %v", err)
224	}
225
226	if len(result) != 1 || result[0].Email != "test@example.com" {
227		t.Errorf("unexpected file contents")
228	}
229}
230
231func TestExportCSVToFile(t *testing.T) {
232	tmpDir := t.TempDir()
233	outputPath := filepath.Join(tmpDir, "contacts.csv")
234
235	contacts := []config.Contact{
236		{
237			Name:     "Test User",
238			Email:    "test@example.com",
239			LastUsed: time.Now(),
240			UseCount: 1,
241		},
242	}
243
244	data, err := exportToCSV(contacts, false)
245	if err != nil {
246		t.Fatalf("exportToCSV failed: %v", err)
247	}
248
249	if err := os.WriteFile(outputPath, data, 0644); err != nil {
250		t.Fatalf("failed to write file: %v", err)
251	}
252
253	// Verify the file was written
254	readData, err := os.ReadFile(outputPath)
255	if err != nil {
256		t.Fatalf("failed to read file: %v", err)
257	}
258
259	output := string(readData)
260	if !contains(output, "test@example.com") {
261		t.Error("expected email in CSV file")
262	}
263}