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