2026-03-03 12:50:24 -06:00
|
|
|
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"])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 10:53:42 -06:00
|
|
|
func TestImportDedup(t *testing.T) {
|
2026-03-03 12:50:24 -06:00
|
|
|
app := testApp(t)
|
|
|
|
|
admin := testAdminUser(t, app)
|
|
|
|
|
token := testToken(t, app, admin)
|
|
|
|
|
mux := testMux(app)
|
|
|
|
|
|
2026-03-04 10:53:42 -06:00
|
|
|
// 3 rows with same order number: first inserts, remaining 2 skip (same external_id)
|
2026-03-03 12:50:24 -06:00
|
|
|
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"])
|
|
|
|
|
}
|
2026-03-04 10:53:42 -06:00
|
|
|
if result["skipped"] != float64(2) {
|
|
|
|
|
t.Errorf("skipped = %v, want 2", result["skipped"])
|
2026-03-03 12:50:24 -06:00
|
|
|
}
|
|
|
|
|
|
2026-03-04 10:53:42 -06:00
|
|
|
tickets, _ := app.listTickets(nil, "")
|
|
|
|
|
if len(tickets) != 1 {
|
|
|
|
|
t.Fatalf("ticket count = %d, want 1", len(tickets))
|
2026-03-03 12:50:24 -06:00
|
|
|
}
|
2026-03-04 10:53:42 -06:00
|
|
|
if tickets[0].Source != "crowdwork" {
|
|
|
|
|
t.Errorf("source = %q, want crowdwork", tickets[0].Source)
|
2026-03-03 12:50:24 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestImportReimportSkips(t *testing.T) {
|
|
|
|
|
app := testApp(t)
|
|
|
|
|
admin := testAdminUser(t, app)
|
|
|
|
|
token := testToken(t, app, admin)
|
|
|
|
|
mux := testMux(app)
|
|
|
|
|
|
2026-03-04 10:53:42 -06:00
|
|
|
// 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"
|
2026-03-03 12:50:24 -06:00
|
|
|
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"])
|
|
|
|
|
}
|
|
|
|
|
}
|