package event import ( "encoding/json" "net/http" "net/http/httptest" "testing" "time" "github.com/bluesky-social/indigo/atproto/syntax" ) func TestInsertCache_InsertsWithToken(t *testing.T) { ctx, db := newTestDB(t) rec := sampleEvent() rec.ExpectedAttendees = 250 token, inserted, err := InsertCache(ctx, db, rec) if err != nil { t.Fatalf("InsertCache: %v", err) } if !inserted { t.Fatal("expected inserted=true for a new event") } if token == "" { t.Fatal("expected a non-empty qr_token to be generated") } // The event must be reachable by its generated QR token. got, err := LookupByQRToken(ctx, db, token) if err != nil { t.Fatalf("LookupByQRToken: %v", err) } if got.URI != rec.URI { t.Errorf("URI = %q, want %q", got.URI, rec.URI) } if got.ExpectedAttendees != 250 { t.Errorf("ExpectedAttendees = %d, want 250", got.ExpectedAttendees) } // created_by_did should be the organizer so the admin UI attributes it. var createdBy string if err := db.QueryRowContext(ctx, `SELECT created_by_did FROM events WHERE uri = ?`, rec.URI).Scan(&createdBy); err != nil { t.Fatalf("read created_by_did: %v", err) } if createdBy != testOrgDID { t.Errorf("created_by_did = %q, want %q", createdBy, testOrgDID) } } func TestInsertCache_SkipsExistingWithoutClobberingToken(t *testing.T) { ctx, db := newTestDB(t) rec := sampleEvent() first, inserted, err := InsertCache(ctx, db, rec) if err != nil || !inserted { t.Fatalf("first InsertCache: token=%q inserted=%v err=%v", first, inserted, err) } // Re-importing the same event must not insert again, and must return the // original token (stable public URL). second, inserted, err := InsertCache(ctx, db, rec) if err != nil { t.Fatalf("second InsertCache: %v", err) } if inserted { t.Error("expected inserted=false on a duplicate import") } if second != first { t.Errorf("token changed on re-import: %q -> %q", first, second) } } func TestListFromPDS_DecodesEvents(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/xrpc/com.atproto.repo.listRecords" { http.NotFound(w, r) return } if got := r.URL.Query().Get("collection"); got != NSID { t.Errorf("collection = %q, want %q", got, NSID) } _ = json.NewEncoder(w).Encode(map[string]any{ "records": []map[string]any{ { "uri": "at://did:plc:eventorganizer/quest.atmo.event/3lkabc", "value": map[string]any{ "$type": NSID, "name": "CascadiaJS 2026", "startTime": "2026-05-31T09:00:00Z", "endTime": "2026-05-31T17:00:00Z", "location": "Portland, OR", "expectedAttendees": 250, }, }, { // Malformed record (no name) should be skipped. "uri": "at://did:plc:eventorganizer/quest.atmo.event/3lkxyz", "value": map[string]any{"$type": NSID}, }, }, "cursor": "", }) })) defer srv.Close() did := syntax.DID(testOrgDID) got, err := ListFromPDS(t.Context(), srv.URL, did) if err != nil { t.Fatalf("ListFromPDS: %v", err) } if len(got) != 1 { t.Fatalf("got %d events, want 1 (malformed should be skipped)", len(got)) } ev := got[0] if ev.Name != "CascadiaJS 2026" { t.Errorf("Name = %q", ev.Name) } if ev.ExpectedAttendees != 250 { t.Errorf("ExpectedAttendees = %d, want 250", ev.ExpectedAttendees) } if !ev.StartTime.Equal(time.Date(2026, 5, 31, 9, 0, 0, 0, time.UTC)) { t.Errorf("StartTime = %v", ev.StartTime) } if ev.OrganizerDID != did { t.Errorf("OrganizerDID = %q, want %q", ev.OrganizerDID, did) } }