package main import ( "bytes" "io" "mime/multipart" "net/http" "net/http/httptest" "testing" ) func postCSV(t *testing.T, mux *http.ServeMux, token, csv string) *httptest.ResponseRecorder { t.Helper() var buf bytes.Buffer writer := multipart.NewWriter(&buf) part, _ := writer.CreateFormFile("csv", "attendees.csv") io.WriteString(part, csv) writer.Close() req := httptest.NewRequest("POST", "/api/import", &buf) req.Header.Set("Content-Type", writer.FormDataContentType()) req.Header.Set("Authorization", "Bearer "+token) w := httptest.NewRecorder() mux.ServeHTTP(w, req) return w } func TestImportCrowdWorkFormat(t *testing.T) { app := testApp(t) admin := testAdminUser(t, app) token := testToken(t, app, admin) mux := testMux(app) csv := "Patron Name,Patron Email,Order Number,Tier Name\nTitania,titania@test.com,ORD-1,GA\nOberon,oberon@test.com,ORD-2,VIP\n" w := postCSV(t, mux, token, csv) if w.Code != http.StatusOK { t.Fatalf("status = %d\nbody: %s", w.Code, w.Body.String()) } result := parseJSON(t, w) if result["inserted"] != float64(2) { t.Errorf("inserted = %v, want 2", result["inserted"]) } } func TestImportGenericFormat(t *testing.T) { app := testApp(t) admin := testAdminUser(t, app) token := testToken(t, app, admin) mux := testMux(app) csv := "name,email,ticket_id,ticket_type,note\nTitania,titania@test.com,T1,GA,VIP guest\n" w := postCSV(t, mux, token, csv) if w.Code != http.StatusOK { t.Fatalf("status = %d", w.Code) } result := parseJSON(t, w) if result["inserted"] != float64(1) { t.Errorf("inserted = %v", result["inserted"]) } } func TestImportDedup(t *testing.T) { app := testApp(t) admin := testAdminUser(t, app) token := testToken(t, app, admin) mux := testMux(app) // 3 rows with same order number: first inserts, remaining 2 skip (same external_id) csv := "Patron Name,Patron Email,Order Number,Tier Name\nTitania,titania@test.com,ORD-1,GA\nTitania,titania@test.com,ORD-1,GA\nTitania,titania@test.com,ORD-1,GA\n" w := postCSV(t, mux, token, csv) result := parseJSON(t, w) if result["inserted"] != float64(1) { t.Errorf("inserted = %v, want 1", result["inserted"]) } if result["skipped"] != float64(2) { t.Errorf("skipped = %v, want 2", result["skipped"]) } tickets, _ := app.listTickets(nil, "") if len(tickets) != 1 { t.Fatalf("ticket count = %d, want 1", len(tickets)) } if tickets[0].Source != "crowdwork" { t.Errorf("source = %q, want crowdwork", tickets[0].Source) } } func TestImportReimportSkips(t *testing.T) { app := testApp(t) admin := testAdminUser(t, app) token := testToken(t, app, admin) mux := testMux(app) // Use ticket_ids so re-import dedup works via UNIQUE(source, external_id) csv := "name,email,ticket_id\nTitania,titania@test.com,T001\nOberon,oberon@test.com,T002\n" postCSV(t, mux, token, csv) // Re-import same data w := postCSV(t, mux, token, csv) result := parseJSON(t, w) if result["inserted"] != float64(0) { t.Errorf("re-import inserted = %v, want 0", result["inserted"]) } if result["skipped"] != float64(2) { t.Errorf("skipped = %v, want 2", result["skipped"]) } } func TestImportMissingNameColumn(t *testing.T) { app := testApp(t) admin := testAdminUser(t, app) token := testToken(t, app, admin) mux := testMux(app) csv := "email,phone\ntitania@test.com,555-1234\n" w := postCSV(t, mux, token, csv) if w.Code != http.StatusBadRequest { t.Errorf("status = %d, want 400", w.Code) } } func TestImportBOM(t *testing.T) { app := testApp(t) admin := testAdminUser(t, app) token := testToken(t, app, admin) mux := testMux(app) // BOM-encoded CSV csv := "\xef\xbb\xbfname,email\nTitania,titania@test.com\n" w := postCSV(t, mux, token, csv) if w.Code != http.StatusOK { t.Fatalf("status = %d", w.Code) } result := parseJSON(t, w) if result["inserted"] != float64(1) { t.Errorf("inserted = %v, want 1", result["inserted"]) } }