Compare commits
No commits in common. "ace7f11a60315a6cf0dd835dff97ba6e6a5cf623" and "d470fb5707b3cd0437af7837baa361ef0ce28f5d" have entirely different histories.
ace7f11a60
...
d470fb5707
9 changed files with 45 additions and 45 deletions
24
db_test.go
24
db_test.go
|
|
@ -20,11 +20,11 @@ func TestMigrate(t *testing.T) {
|
|||
func TestAttendeesCRUD(t *testing.T) {
|
||||
app := testApp(t)
|
||||
|
||||
a, err := app.createAttendee(Attendee{Name: "Titania", Email: "titania@test.com", TicketType: "GA"})
|
||||
a, err := app.createAttendee(Attendee{Name: "Alice", Email: "alice@test.com", TicketType: "GA"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a.ID == 0 || a.Name != "Titania" {
|
||||
if a.ID == 0 || a.Name != "Alice" {
|
||||
t.Errorf("create: got %+v", a)
|
||||
}
|
||||
|
||||
|
|
@ -32,16 +32,16 @@ func TestAttendeesCRUD(t *testing.T) {
|
|||
if err != nil || got == nil {
|
||||
t.Fatal("get: not found")
|
||||
}
|
||||
if got.Email != "titania@test.com" {
|
||||
if got.Email != "alice@test.com" {
|
||||
t.Errorf("get: email = %q", got.Email)
|
||||
}
|
||||
|
||||
got.Name = "Titania Fairweather"
|
||||
got.Name = "Alice Smith"
|
||||
if err := app.updateAttendee(*got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got2, _ := app.getAttendee(a.ID)
|
||||
if got2.Name != "Titania Fairweather" {
|
||||
if got2.Name != "Alice Smith" {
|
||||
t.Errorf("update: name = %q", got2.Name)
|
||||
}
|
||||
|
||||
|
|
@ -60,9 +60,9 @@ func TestAttendeesCRUD(t *testing.T) {
|
|||
func TestIncrementPartySize(t *testing.T) {
|
||||
app := testApp(t)
|
||||
|
||||
app.createAttendee(Attendee{Name: "Oberon", TicketID: "ORD-100"})
|
||||
app.createAttendee(Attendee{Name: "Bob", TicketID: "ORD-100"})
|
||||
|
||||
merged, err := app.incrementPartySize("Oberon", "ORD-100")
|
||||
merged, err := app.incrementPartySize("Bob", "ORD-100")
|
||||
if err != nil || !merged {
|
||||
t.Fatalf("increment: merged=%v, err=%v", merged, err)
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ func TestIncrementPartySize(t *testing.T) {
|
|||
}
|
||||
|
||||
// Different ticket_id should not merge
|
||||
merged2, _ := app.incrementPartySize("Oberon", "ORD-200")
|
||||
merged2, _ := app.incrementPartySize("Bob", "ORD-200")
|
||||
if merged2 {
|
||||
t.Error("should not merge different ticket_id")
|
||||
}
|
||||
|
|
@ -83,7 +83,7 @@ func TestCheckInAttendee(t *testing.T) {
|
|||
app := testApp(t)
|
||||
admin := testAdminUser(t, app)
|
||||
|
||||
app.createAttendee(Attendee{Name: "Puck"})
|
||||
app.createAttendee(Attendee{Name: "Charlie"})
|
||||
// Set party_size directly since createAttendee defaults to 1
|
||||
app.db.Exec(`UPDATE attendees SET party_size = 3 WHERE id = 1`)
|
||||
|
||||
|
|
@ -197,7 +197,7 @@ func TestAssignAndUnassignShift(t *testing.T) {
|
|||
dept, _ := app.createDepartment(Department{Name: "Gate"})
|
||||
deptID := dept.ID
|
||||
s, _ := app.createShift(Shift{DepartmentID: deptID, Name: "AM", Day: "2026-03-15", StartTime: "08:00", EndTime: "12:00", Capacity: 2})
|
||||
v, _ := app.createVolunteer(Volunteer{Name: "Helena", DepartmentID: &deptID})
|
||||
v, _ := app.createVolunteer(Volunteer{Name: "Dana", DepartmentID: &deptID})
|
||||
|
||||
if err := app.assignShift(v.ID, s.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -221,7 +221,7 @@ func TestCheckShiftConflict(t *testing.T) {
|
|||
|
||||
dept, _ := app.createDepartment(Department{Name: "Gate"})
|
||||
deptID := dept.ID
|
||||
v, _ := app.createVolunteer(Volunteer{Name: "Hermia", DepartmentID: &deptID})
|
||||
v, _ := app.createVolunteer(Volunteer{Name: "Eve", DepartmentID: &deptID})
|
||||
|
||||
s1, _ := app.createShift(Shift{DepartmentID: deptID, Name: "Morning", Day: "2026-03-15", StartTime: "08:00", EndTime: "12:00"})
|
||||
s2, _ := app.createShift(Shift{DepartmentID: deptID, Name: "Overlap", Day: "2026-03-15", StartTime: "10:00", EndTime: "14:00"})
|
||||
|
|
@ -250,7 +250,7 @@ func TestCheckShiftConflictMidnight(t *testing.T) {
|
|||
|
||||
dept, _ := app.createDepartment(Department{Name: "Sound"})
|
||||
deptID := dept.ID
|
||||
v, _ := app.createVolunteer(Volunteer{Name: "Lysander", DepartmentID: &deptID})
|
||||
v, _ := app.createVolunteer(Volunteer{Name: "Frank", DepartmentID: &deptID})
|
||||
|
||||
// Night shift: 22:00-02:00 (spans midnight)
|
||||
night, _ := app.createShift(Shift{DepartmentID: deptID, Name: "Night", Day: "2026-03-15", StartTime: "22:00", EndTime: "02:00"})
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ Column matching is case-insensitive. Extra columns are ignored. BOM-encoded file
|
|||
|
||||
CrowdWork exports one row per ticket, even when the same person bought multiple tickets in one order. Turnpike handles this automatically:
|
||||
|
||||
- First row for "Titania Fairweather" (order 1234) creates a record with `party_size=1`
|
||||
- First row for "Alice Smith" (order 1234) creates a record with `party_size=1`
|
||||
- Subsequent rows with the same name + order number increment `party_size` (no duplicate record)
|
||||
- Result: one attendee record, `party_size=3` if three tickets were purchased
|
||||
|
||||
|
|
|
|||
|
|
@ -50,9 +50,9 @@ describe('apiFetch', () => {
|
|||
|
||||
describe('apiJSON', () => {
|
||||
it('parses JSON response', async () => {
|
||||
mockFetch({ name: 'Titania' })
|
||||
mockFetch({ name: 'Alice' })
|
||||
const result = await apiJSON('/api/test')
|
||||
expect(result.name).toBe('Titania')
|
||||
expect(result.name).toBe('Alice')
|
||||
})
|
||||
|
||||
it('throws on non-OK response', async () => {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ describe('syncPull', () => {
|
|||
it('writes attendees to Dexie', async () => {
|
||||
mockFetch({
|
||||
server_time: '2026-03-01T12:00:00Z',
|
||||
attendees: [{ id: 1, name: 'Titania' }],
|
||||
attendees: [{ id: 1, name: 'Alice' }],
|
||||
departments: [],
|
||||
volunteers: [],
|
||||
shifts: [],
|
||||
|
|
@ -32,16 +32,16 @@ describe('syncPull', () => {
|
|||
await syncPull()
|
||||
|
||||
const a = await db.attendees.get(1)
|
||||
expect(a.name).toBe('Titania')
|
||||
expect(a.name).toBe('Alice')
|
||||
expect(await getLastSync()).toBe('2026-03-01T12:00:00Z')
|
||||
})
|
||||
|
||||
it('deletes soft-deleted attendees from Dexie', async () => {
|
||||
await db.attendees.put({ id: 1, name: 'Titania' })
|
||||
await db.attendees.put({ id: 1, name: 'Alice' })
|
||||
|
||||
mockFetch({
|
||||
server_time: '2026-03-01T13:00:00Z',
|
||||
attendees: [{ id: 1, name: 'Titania', deleted_at: '2026-03-01T12:30:00Z' }],
|
||||
attendees: [{ id: 1, name: 'Alice', deleted_at: '2026-03-01T12:30:00Z' }],
|
||||
departments: [],
|
||||
volunteers: [],
|
||||
shifts: [],
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ func TestAttendeesListCreateDelete(t *testing.T) {
|
|||
mux := testMux(app)
|
||||
|
||||
// Create
|
||||
req := testAuthRequest("POST", "/api/attendees", map[string]string{"name": "Titania"}, token)
|
||||
req := testAuthRequest("POST", "/api/attendees", map[string]string{"name": "Alice"}, token)
|
||||
w := httptest.NewRecorder()
|
||||
mux.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusCreated {
|
||||
|
|
@ -59,7 +59,7 @@ func TestCheckInAttendeeHandler(t *testing.T) {
|
|||
token := testToken(t, app, admin)
|
||||
mux := testMux(app)
|
||||
|
||||
app.createAttendee(Attendee{Name: "Oberon"})
|
||||
app.createAttendee(Attendee{Name: "Bob"})
|
||||
app.db.Exec(`UPDATE attendees SET party_size = 3 WHERE id = 1`)
|
||||
|
||||
// Check in 1
|
||||
|
|
@ -82,7 +82,7 @@ func TestGateRoleCanCheckIn(t *testing.T) {
|
|||
token := testToken(t, app, gate)
|
||||
mux := testMux(app)
|
||||
|
||||
app.createAttendee(Attendee{Name: "Puck"})
|
||||
app.createAttendee(Attendee{Name: "Charlie"})
|
||||
|
||||
req := testAuthRequest("POST", "/api/attendees/1/checkin", nil, token)
|
||||
w := httptest.NewRecorder()
|
||||
|
|
@ -98,7 +98,7 @@ func TestGateRoleCannotDelete(t *testing.T) {
|
|||
token := testToken(t, app, gate)
|
||||
mux := testMux(app)
|
||||
|
||||
app.createAttendee(Attendee{Name: "Puck"})
|
||||
app.createAttendee(Attendee{Name: "Charlie"})
|
||||
|
||||
req := testAuthRequest("DELETE", "/api/attendees/1", nil, token)
|
||||
w := httptest.NewRecorder()
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ func TestImportCrowdWorkFormat(t *testing.T) {
|
|||
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"
|
||||
csv := "Patron Name,Patron Email,Order Number,Tier Name\nAlice,alice@test.com,ORD-1,GA\nBob,bob@test.com,ORD-2,VIP\n"
|
||||
w := postCSV(t, mux, token, csv)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
|
|
@ -49,7 +49,7 @@ func TestImportGenericFormat(t *testing.T) {
|
|||
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"
|
||||
csv := "name,email,ticket_id,ticket_type,note\nAlice,alice@test.com,T1,GA,VIP guest\n"
|
||||
w := postCSV(t, mux, token, csv)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
|
|
@ -68,7 +68,7 @@ func TestImportPartySizeDedup(t *testing.T) {
|
|||
mux := testMux(app)
|
||||
|
||||
// 3 rows same name+order = 1 record, party_size=3
|
||||
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"
|
||||
csv := "Patron Name,Patron Email,Order Number,Tier Name\nAlice,alice@test.com,ORD-1,GA\nAlice,alice@test.com,ORD-1,GA\nAlice,alice@test.com,ORD-1,GA\n"
|
||||
w := postCSV(t, mux, token, csv)
|
||||
|
||||
result := parseJSON(t, w)
|
||||
|
|
@ -94,7 +94,7 @@ func TestImportReimportSkips(t *testing.T) {
|
|||
token := testToken(t, app, admin)
|
||||
mux := testMux(app)
|
||||
|
||||
csv := "name\nTitania\nOberon\n"
|
||||
csv := "name\nAlice\nBob\n"
|
||||
postCSV(t, mux, token, csv)
|
||||
|
||||
// Re-import same data
|
||||
|
|
@ -114,7 +114,7 @@ func TestImportMissingNameColumn(t *testing.T) {
|
|||
token := testToken(t, app, admin)
|
||||
mux := testMux(app)
|
||||
|
||||
csv := "email,phone\ntitania@test.com,555-1234\n"
|
||||
csv := "email,phone\nalice@test.com,555-1234\n"
|
||||
w := postCSV(t, mux, token, csv)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
|
|
@ -129,7 +129,7 @@ func TestImportBOM(t *testing.T) {
|
|||
mux := testMux(app)
|
||||
|
||||
// BOM-encoded CSV
|
||||
csv := "\xef\xbb\xbfname,email\nTitania,titania@test.com\n"
|
||||
csv := "\xef\xbb\xbfname,email\nAlice,alice@test.com\n"
|
||||
w := postCSV(t, mux, token, csv)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ func setupKiosk(t *testing.T) (*App, *http.ServeMux, string) {
|
|||
deptID := dept.ID
|
||||
|
||||
// Create attendee with token
|
||||
a, _ := app.createAttendee(Attendee{Name: "Titania", Email: "titania@test.com"})
|
||||
a, _ := app.createAttendee(Attendee{Name: "Alice", Email: "alice@test.com"})
|
||||
token, _ := app.generateUniqueToken()
|
||||
app.db.Exec(`UPDATE attendees SET volunteer_token = ? WHERE id = ?`, token, a.ID)
|
||||
|
||||
// Create linked volunteer
|
||||
app.createVolunteer(Volunteer{Name: "Titania", AttendeeID: &a.ID, DepartmentID: &deptID})
|
||||
app.createVolunteer(Volunteer{Name: "Alice", AttendeeID: &a.ID, DepartmentID: &deptID})
|
||||
|
||||
// Create shifts
|
||||
app.createShift(Shift{DepartmentID: deptID, Name: "Morning", Day: "2026-03-15", StartTime: "08:00", EndTime: "12:00", Capacity: 2})
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func TestShiftAssignVolunteer(t *testing.T) {
|
|||
dept, _ := app.createDepartment(Department{Name: "Gate"})
|
||||
deptID := dept.ID
|
||||
app.createShift(Shift{DepartmentID: deptID, Name: "AM", Day: "2026-03-15", StartTime: "08:00", EndTime: "12:00"})
|
||||
app.createVolunteer(Volunteer{Name: "Titania", DepartmentID: &deptID})
|
||||
app.createVolunteer(Volunteer{Name: "Alice", DepartmentID: &deptID})
|
||||
|
||||
// Assign
|
||||
req := testAuthRequest("POST", "/api/shifts/1/volunteers", map[string]any{
|
||||
|
|
@ -86,7 +86,7 @@ func TestShiftAssignConflict(t *testing.T) {
|
|||
deptID := dept.ID
|
||||
app.createShift(Shift{DepartmentID: deptID, Name: "AM", Day: "2026-03-15", StartTime: "08:00", EndTime: "12:00"})
|
||||
app.createShift(Shift{DepartmentID: deptID, Name: "Overlap", Day: "2026-03-15", StartTime: "10:00", EndTime: "14:00"})
|
||||
app.createVolunteer(Volunteer{Name: "Titania", DepartmentID: &deptID})
|
||||
app.createVolunteer(Volunteer{Name: "Alice", DepartmentID: &deptID})
|
||||
|
||||
// Assign to first shift
|
||||
app.assignShift(1, 1)
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ func TestSyncPullFull(t *testing.T) {
|
|||
token := testToken(t, app, admin)
|
||||
mux := testMux(app)
|
||||
|
||||
app.createAttendee(Attendee{Name: "Titania"})
|
||||
app.createAttendee(Attendee{Name: "Alice"})
|
||||
dept, _ := app.createDepartment(Department{Name: "Gate"})
|
||||
deptID := dept.ID
|
||||
app.createVolunteer(Volunteer{Name: "Titania", DepartmentID: &deptID})
|
||||
app.createVolunteer(Volunteer{Name: "Alice", DepartmentID: &deptID})
|
||||
app.createShift(Shift{DepartmentID: deptID, Name: "AM", Day: "2026-03-15", StartTime: "08:00", EndTime: "12:00"})
|
||||
|
||||
req := testAuthRequest("GET", "/api/sync/pull", nil, token)
|
||||
|
|
@ -47,14 +47,14 @@ func TestSyncPullIncremental(t *testing.T) {
|
|||
token := testToken(t, app, admin)
|
||||
mux := testMux(app)
|
||||
|
||||
app.createAttendee(Attendee{Name: "Titania"})
|
||||
// Backdate Titania so she falls before the "since" cutoff
|
||||
app.db.Exec(`UPDATE attendees SET updated_at = '2026-01-01T00:00:00Z' WHERE name = 'Titania'`)
|
||||
app.createAttendee(Attendee{Name: "Alice"})
|
||||
// Backdate Alice so she falls before the "since" cutoff
|
||||
app.db.Exec(`UPDATE attendees SET updated_at = '2026-01-01T00:00:00Z' WHERE name = 'Alice'`)
|
||||
|
||||
since := "2026-01-01T12:00:00Z"
|
||||
|
||||
// Oberon created with default updated_at (now), which is after our since
|
||||
app.createAttendee(Attendee{Name: "Oberon"})
|
||||
// Bob created with default updated_at (now), which is after our since
|
||||
app.createAttendee(Attendee{Name: "Bob"})
|
||||
|
||||
req := testAuthRequest("GET", "/api/sync/pull?since="+since, nil, token)
|
||||
w := httptest.NewRecorder()
|
||||
|
|
@ -62,14 +62,14 @@ func TestSyncPullIncremental(t *testing.T) {
|
|||
|
||||
result := parseJSON(t, w)
|
||||
attendees := result["attendees"].([]any)
|
||||
// Should only include Oberon (created after `since`)
|
||||
// Should only include Bob (created after `since`)
|
||||
if len(attendees) != 1 {
|
||||
t.Errorf("incremental: got %d attendees, want 1", len(attendees))
|
||||
}
|
||||
if len(attendees) == 1 {
|
||||
a := attendees[0].(map[string]any)
|
||||
if a["name"] != "Oberon" {
|
||||
t.Errorf("name = %v, want Oberon", a["name"])
|
||||
if a["name"] != "Bob" {
|
||||
t.Errorf("name = %v, want Bob", a["name"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -80,8 +80,8 @@ func TestSyncPullIncludesSoftDeleted(t *testing.T) {
|
|||
token := testToken(t, app, admin)
|
||||
mux := testMux(app)
|
||||
|
||||
a, _ := app.createAttendee(Attendee{Name: "Titania"})
|
||||
// Backdate Titania's creation so the since cutoff is between creation and deletion
|
||||
a, _ := app.createAttendee(Attendee{Name: "Alice"})
|
||||
// Backdate Alice's creation so the since cutoff is between creation and deletion
|
||||
app.db.Exec(`UPDATE attendees SET updated_at = '2026-01-01T00:00:00Z' WHERE id = ?`, a.ID)
|
||||
|
||||
since := "2026-01-01T12:00:00Z"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue