//go:build test && dev package dav import ( "context" "fmt" "testing" "time" "crussell/clock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // ============================================================================ // Pure Function Tests: extractContactURIsFromICalendar // ============================================================================ func TestExtractContactURIsFromICalendar_Empty(t *testing.T) { result := extractContactURIsFromICalendar("") assert.Empty(t, result) } func TestExtractContactURIsFromICalendar_SingleAttendee(t *testing.T) { ical := "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nUID:1\nATTENDEE:mailto:user@example.com\nEND:VEVENT\nEND:VCALENDAR" result := extractContactURIsFromICalendar(ical) assert.Equal(t, []string{"mailto:user@example.com"}, result) } func TestExtractContactURIsFromICalendar_MultipleAttendees(t *testing.T) { ical := "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nUID:1\nATTENDEE:mailto:alice@example.com\nATTENDEE:mailto:bob@example.com\nEND:VEVENT\nEND:VCALENDAR" result := extractContactURIsFromICalendar(ical) assert.Equal(t, []string{"mailto:alice@example.com", "mailto:bob@example.com"}, result) } func TestExtractContactURIsFromICalendar_NoAttendee(t *testing.T) { ical := "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nUID:1\nSUMMARY:Test\nEND:VEVENT\nEND:VCALENDAR" result := extractContactURIsFromICalendar(ical) assert.Empty(t, result) } func TestExtractContactURIsFromICalendar_AttendeeWithCN(t *testing.T) { ical := "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nUID:1\nATTENDEE;CN=John Doe:mailto:john@example.com\nEND:VEVENT\nEND:VCALENDAR" result := extractContactURIsFromICalendar(ical) assert.Equal(t, []string{"mailto:john@example.com"}, result) } func TestExtractContactURIsFromICalendar_AttendeeNoColon(t *testing.T) { ical := "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nUID:1\nATTENDEE\nEND:VEVENT\nEND:VCALENDAR" result := extractContactURIsFromICalendar(ical) assert.Empty(t, result) } // ============================================================================ // Read-Only DB Query Tests // ============================================================================ func TestGetContactByURI_Found(t *testing.T) { ctx := context.Background() uri := "test-contact-found-" + t.Name() now := time.Now().Unix() t.Cleanup(func() { _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_cards WHERE addressbookid = $1 AND uri = $2", 1, uri) }) _, err := testSvc.db.Exec(ctx, `INSERT INTO dav_cards (addressbookid, uri, carddata, lastmodified, etag, size) VALUES ($1, $2, $3, $4, $5, $6)`, 1, uri, "BEGIN:VCARD\nVERSION:3.0\nUID:test\nFN:Test User\nEND:VCARD", now, fmt.Sprintf("%d", now), 100, ) require.NoError(t, err) contact, err := testSvc.GetContactByURI(1, uri) require.NoError(t, err) require.NotNil(t, contact) assert.Equal(t, 1, contact.AddressBookID) assert.Equal(t, uri, contact.URI) assert.Equal(t, now, contact.LastModified) } func TestGetContactByURI_NotFound(t *testing.T) { _, err := testSvc.GetContactByURI(1, "non-existent-uri-"+t.Name()) require.Error(t, err) assert.Contains(t, err.Error(), "contact not found") } func TestListAllContacts_Empty(t *testing.T) { ctx := context.Background() _, err := testSvc.db.Exec(ctx, "DELETE FROM dav_cards WHERE uri LIKE 'test-%'") require.NoError(t, err) contacts, err := testSvc.ListAllContacts() require.NoError(t, err) assert.Empty(t, contacts) } func TestListAllContacts(t *testing.T) { ctx := context.Background() now := time.Now().Unix() uris := []string{ "test-contact-list-1-" + t.Name(), "test-contact-list-2-" + t.Name(), "test-contact-list-3-" + t.Name(), } for _, uri := range uris { _, err := testSvc.db.Exec(ctx, `INSERT INTO dav_cards (addressbookid, uri, carddata, lastmodified, etag, size) VALUES ($1, $2, $3, $4, $5, $6)`, 1, uri, "BEGIN:VCARD\nVERSION:3.0\nUID:test\nFN:Test User\nEND:VCARD", now, fmt.Sprintf("%d", now), 100, ) require.NoError(t, err) } t.Cleanup(func() { for _, uri := range uris { _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_cards WHERE addressbookid = $1 AND uri = $2", 1, uri) } }) contacts, err := testSvc.ListAllContacts() require.NoError(t, err) uriSet := make(map[string]bool) for _, u := range uris { uriSet[u] = true } found := 0 for _, c := range contacts { if uriSet[c.URI] { found++ } } assert.Equal(t, 3, found) } func TestListEventsForMonth(t *testing.T) { ctx := context.Background() now := time.Now().Unix() julyEventUID := "test-july-event-" + t.Name() augEventUID := "test-aug-event-" + t.Name() julyStart := time.Date(2026, 7, 15, 9, 0, 0, 0, time.UTC) julyEnd := time.Date(2026, 7, 15, 10, 0, 0, 0, time.UTC) augStart := time.Date(2026, 8, 15, 9, 0, 0, 0, time.UTC) augEnd := time.Date(2026, 8, 15, 10, 0, 0, 0, time.UTC) julyCalData := "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nUID:" + julyEventUID + "\nDTSTART:20260715T090000Z\nDTEND:20260715T100000Z\nSUMMARY:July Event\nEND:VEVENT\nEND:VCALENDAR" augCalData := "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nUID:" + augEventUID + "\nDTSTART:20260815T090000Z\nDTEND:20260815T100000Z\nSUMMARY:August Event\nEND:VEVENT\nEND:VCALENDAR" _, err := testSvc.db.Exec(ctx, `INSERT INTO dav_calendarobjects (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES ($1, $2, $3, $4, $5, $6, 'VEVENT', $7, $8, $9)`, 1, julyEventUID+".ics", julyCalData, now, fmt.Sprintf("%d", now), len(julyCalData), julyStart.Unix(), julyEnd.Unix(), julyEventUID, ) require.NoError(t, err) _, err = testSvc.db.Exec(ctx, `INSERT INTO dav_calendarobjects (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES ($1, $2, $3, $4, $5, $6, 'VEVENT', $7, $8, $9)`, 1, augEventUID+".ics", augCalData, now, fmt.Sprintf("%d", now), len(augCalData), augStart.Unix(), augEnd.Unix(), augEventUID, ) require.NoError(t, err) t.Cleanup(func() { _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_calendarobjects WHERE uid = $1", julyEventUID) _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_calendarobjects WHERE uid = $1", augEventUID) }) events, err := testSvc.ListEventsForMonth(2026, time.July) require.NoError(t, err) var foundJuly, foundAug bool for _, e := range events { if e.UID == julyEventUID { foundJuly = true } if e.UID == augEventUID { foundAug = true } } assert.True(t, foundJuly, "July event should be found for July 2026 query") assert.False(t, foundAug, "August event should not be found for July 2026 query") } func TestListEventsBetween(t *testing.T) { ctx := context.Background() now := time.Now().Unix() julyEventUID := "test-july-between-" + t.Name() augEventUID := "test-aug-between-" + t.Name() julyStart := time.Date(2026, 7, 15, 9, 0, 0, 0, time.UTC) julyEnd := time.Date(2026, 7, 15, 10, 0, 0, 0, time.UTC) augStart := time.Date(2026, 8, 15, 9, 0, 0, 0, time.UTC) augEnd := time.Date(2026, 8, 15, 10, 0, 0, 0, time.UTC) julyCalData := "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nUID:" + julyEventUID + "\nDTSTART:20260715T090000Z\nDTEND:20260715T100000Z\nSUMMARY:July Event\nEND:VEVENT\nEND:VCALENDAR" augCalData := "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nUID:" + augEventUID + "\nDTSTART:20260815T090000Z\nDTEND:20260815T100000Z\nSUMMARY:August Event\nEND:VEVENT\nEND:VCALENDAR" _, err := testSvc.db.Exec(ctx, `INSERT INTO dav_calendarobjects (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES ($1, $2, $3, $4, $5, $6, 'VEVENT', $7, $8, $9)`, 1, julyEventUID+".ics", julyCalData, now, fmt.Sprintf("%d", now), len(julyCalData), julyStart.Unix(), julyEnd.Unix(), julyEventUID, ) require.NoError(t, err) _, err = testSvc.db.Exec(ctx, `INSERT INTO dav_calendarobjects (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES ($1, $2, $3, $4, $5, $6, 'VEVENT', $7, $8, $9)`, 1, augEventUID+".ics", augCalData, now, fmt.Sprintf("%d", now), len(augCalData), augStart.Unix(), augEnd.Unix(), augEventUID, ) require.NoError(t, err) t.Cleanup(func() { _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_calendarobjects WHERE uid = $1", julyEventUID) _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_calendarobjects WHERE uid = $1", augEventUID) }) start := time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC) end := time.Date(2026, 7, 31, 23, 59, 59, 0, time.UTC) events, err := testSvc.ListEventsBetween(start, end) require.NoError(t, err) var foundJuly, foundAug bool for _, e := range events { if e.UID == julyEventUID { foundJuly = true } if e.UID == augEventUID { foundAug = true } } assert.True(t, foundJuly, "July event should be found within July range") assert.False(t, foundAug, "August event should not be found within July range") } // ============================================================================ // Part A: Time-Sensitive Query Tests // ============================================================================ func TestListEventsTomorrow(t *testing.T) { ctx := context.Background() now := clock.Now() tomorrow := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, time.UTC) inUID := "test-tomorrow-in-" + t.Name() inStart := tomorrow.Add(2 * time.Hour) inEnd := inStart.Add(1 * time.Hour) inCalData := "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nUID:" + inUID + "\nDTSTART:" + inStart.Format("20060102T150405Z") + "\nDTEND:" + inEnd.Format("20060102T150405Z") + "\nSUMMARY:Tomorrow Event\nEND:VEVENT\nEND:VCALENDAR" outUID := "test-tomorrow-out-" + t.Name() yesterday := now.AddDate(0, 0, -1) outStart := time.Date(yesterday.Year(), yesterday.Month(), yesterday.Day(), 12, 0, 0, 0, time.UTC) outEnd := outStart.Add(1 * time.Hour) outCalData := "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nUID:" + outUID + "\nDTSTART:" + outStart.Format("20060102T150405Z") + "\nDTEND:" + outEnd.Format("20060102T150405Z") + "\nSUMMARY:Yesterday Event\nEND:VEVENT\nEND:VCALENDAR" _, err := testSvc.db.Exec(ctx, `INSERT INTO dav_calendarobjects (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES ($1, $2, $3, $4, $5, $6, 'VEVENT', $7, $8, $9)`, 1, inUID+".ics", inCalData, now.Unix(), fmt.Sprintf("%d", now.Unix()), len(inCalData), inStart.Unix(), inEnd.Unix(), inUID, ) require.NoError(t, err) _, err = testSvc.db.Exec(ctx, `INSERT INTO dav_calendarobjects (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES ($1, $2, $3, $4, $5, $6, 'VEVENT', $7, $8, $9)`, 1, outUID+".ics", outCalData, now.Unix(), fmt.Sprintf("%d", now.Unix()), len(outCalData), outStart.Unix(), outEnd.Unix(), outUID, ) require.NoError(t, err) t.Cleanup(func() { _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_calendarobjects WHERE uid = $1", inUID) _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_calendarobjects WHERE uid = $1", outUID) }) events, err := testSvc.ListEventsTomorrow() require.NoError(t, err) var foundIn, foundOut bool for _, e := range events { if e.UID == inUID { foundIn = true } if e.UID == outUID { foundOut = true } } assert.True(t, foundIn, "event with firstoccurence tomorrow should be returned") assert.False(t, foundOut, "event with firstoccurence yesterday should not be returned") } func TestListEventsThisWeek(t *testing.T) { ctx := context.Background() now := clock.Now() weekday := int(now.Weekday()) if weekday == 0 { weekday = 7 } monday := now.AddDate(0, 0, -weekday+1) monday = time.Date(monday.Year(), monday.Month(), monday.Day(), 0, 0, 0, 0, time.UTC) inUID := "test-week-in-" + t.Name() inStart := monday.Add(48 * time.Hour) inEnd := inStart.Add(1 * time.Hour) inCalData := "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nUID:" + inUID + "\nDTSTART:" + inStart.Format("20060102T150405Z") + "\nDTEND:" + inEnd.Format("20060102T150405Z") + "\nSUMMARY:This Week Event\nEND:VEVENT\nEND:VCALENDAR" outUID := "test-week-out-" + t.Name() outStart := monday.AddDate(0, 0, -2) outEnd := outStart.Add(1 * time.Hour) outCalData := "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nUID:" + outUID + "\nDTSTART:" + outStart.Format("20060102T150405Z") + "\nDTEND:" + outEnd.Format("20060102T150405Z") + "\nSUMMARY:Last Week Event\nEND:VEVENT\nEND:VCALENDAR" _, err := testSvc.db.Exec(ctx, `INSERT INTO dav_calendarobjects (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES ($1, $2, $3, $4, $5, $6, 'VEVENT', $7, $8, $9)`, 1, inUID+".ics", inCalData, now.Unix(), fmt.Sprintf("%d", now.Unix()), len(inCalData), inStart.Unix(), inEnd.Unix(), inUID, ) require.NoError(t, err) _, err = testSvc.db.Exec(ctx, `INSERT INTO dav_calendarobjects (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES ($1, $2, $3, $4, $5, $6, 'VEVENT', $7, $8, $9)`, 1, outUID+".ics", outCalData, now.Unix(), fmt.Sprintf("%d", now.Unix()), len(outCalData), outStart.Unix(), outEnd.Unix(), outUID, ) require.NoError(t, err) t.Cleanup(func() { _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_calendarobjects WHERE uid = $1", inUID) _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_calendarobjects WHERE uid = $1", outUID) }) events, err := testSvc.ListEventsThisWeek() require.NoError(t, err) var foundIn, foundOut bool for _, e := range events { if e.UID == inUID { foundIn = true } if e.UID == outUID { foundOut = true } } assert.True(t, foundIn, "event within this week should be returned") assert.False(t, foundOut, "event outside this week should not be returned") } func TestListRecentContacts(t *testing.T) { ctx := context.Background() now := time.Now() recentURI := "test-recent-contact-" + t.Name() oldURI := "test-old-contact-" + t.Name() recentLastModified := now.AddDate(0, 0, -5).Unix() oldLastModified := now.AddDate(0, 0, -60).Unix() _, err := testSvc.db.Exec(ctx, `INSERT INTO dav_cards (addressbookid, uri, carddata, lastmodified, etag, size) VALUES ($1, $2, $3, $4, $5, $6)`, 1, recentURI, "BEGIN:VCARD\nVERSION:3.0\nFN:Recent\nEND:VCARD", recentLastModified, fmt.Sprintf("%d", recentLastModified), 100, ) require.NoError(t, err) _, err = testSvc.db.Exec(ctx, `INSERT INTO dav_cards (addressbookid, uri, carddata, lastmodified, etag, size) VALUES ($1, $2, $3, $4, $5, $6)`, 1, oldURI, "BEGIN:VCARD\nVERSION:3.0\nFN:Old\nEND:VCARD", oldLastModified, fmt.Sprintf("%d", oldLastModified), 100, ) require.NoError(t, err) t.Cleanup(func() { _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_cards WHERE uri = $1", recentURI) _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_cards WHERE uri = $1", oldURI) }) contacts, err := testSvc.ListRecentContacts(30) require.NoError(t, err) var foundRecent, foundOld bool for _, c := range contacts { if c.URI == recentURI { foundRecent = true } if c.URI == oldURI { foundOld = true } } assert.True(t, foundRecent, "recent contact (5 days old) should be returned with 30-day cutoff") assert.False(t, foundOld, "old contact (60 days old) should not be returned with 30-day cutoff") } func TestListEventsForContact(t *testing.T) { ctx := context.Background() now := time.Now() contactURI := "mailto:test-" + t.Name() + "@test.com" eventUID := "test-contact-event-" + t.Name() calData := "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nUID:" + eventUID + "\nATTENDEE:" + contactURI + "\nSUMMARY:Contact Event\nEND:VEVENT\nEND:VCALENDAR" start := now.Add(24 * time.Hour) end := start.Add(1 * time.Hour) _, err := testSvc.db.Exec(ctx, `INSERT INTO dav_calendarobjects (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES ($1, $2, $3, $4, $5, $6, 'VEVENT', $7, $8, $9)`, 1, eventUID+".ics", calData, now.Unix(), fmt.Sprintf("%d", now.Unix()), len(calData), start.Unix(), end.Unix(), eventUID, ) require.NoError(t, err) t.Cleanup(func() { _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_calendarobjects WHERE uid = $1", eventUID) }) events, err := testSvc.ListEventsForContact(contactURI) require.NoError(t, err) var found bool for _, e := range events { if e.UID == eventUID { found = true break } } assert.True(t, found, "event with matching contact URI in calendardata should be returned") } // ============================================================================ // Part B: Transactional Mutation Tests // ============================================================================ func TestCreateContact_Success(t *testing.T) { ctx := context.Background() userID := "test-create-contact-" + t.Name() uri := userID + ".vcf" input := ContactInput{ UserID: userID, FirstName: "John", LastName: "Doe", Email: "john@test.com", Phone: "1234567890", DOB: "1990-01-01", } err := testSvc.CreateContact(1, userID, input) require.NoError(t, err) t.Cleanup(func() { _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_cards WHERE addressbookid = $1 AND uri = $2", 1, uri) }) var id, addressBookID, size int var cardData, scannedURI, etag string var lastModified int64 err = testSvc.db.QueryRow(ctx, "SELECT id, addressbookid, uri, carddata, lastmodified, etag, size FROM dav_cards WHERE addressbookid = $1 AND uri = $2", 1, uri, ).Scan(&id, &addressBookID, &scannedURI, &cardData, &lastModified, &etag, &size) require.NoError(t, err, "inserted contact should be queryable") assert.Equal(t, 1, addressBookID) assert.Equal(t, uri, scannedURI) assert.Equal(t, len(cardData), size) assert.Equal(t, etag, fmt.Sprintf("%d", lastModified)) assert.Contains(t, cardData, "FN:John Doe") assert.Contains(t, cardData, "N:Doe;John;;;") assert.Contains(t, cardData, "EMAIL;TYPE=INTERNET:john@test.com") assert.Contains(t, cardData, "TEL;TYPE=CELL:1234567890") assert.Contains(t, cardData, "BDAY:1990-01-01") } func TestUpdateContact_Success(t *testing.T) { ctx := context.Background() userID := "test-update-contact-" + t.Name() uri := userID + ".vcf" now := time.Now().Unix() _, err := testSvc.db.Exec(ctx, `INSERT INTO dav_cards (addressbookid, uri, carddata, lastmodified, etag, size) VALUES ($1, $2, $3, $4, $5, $6)`, 1, uri, "BEGIN:VCARD\nVERSION:3.0\nFN:Old Name\nEND:VCARD", now, fmt.Sprintf("%d", now), 100, ) require.NoError(t, err) t.Cleanup(func() { _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_cards WHERE addressbookid = $1 AND uri = $2", 1, uri) }) updatedInput := ContactInput{ UserID: userID, FirstName: "Jane", LastName: "Doe", Email: "jane@test.com", Phone: "0987654321", DOB: "1995-05-05", } err = testSvc.UpdateContact(1, uri, updatedInput) require.NoError(t, err) var cardData string var lastModified int64 var etag string var size int err = testSvc.db.QueryRow(ctx, "SELECT carddata, lastmodified, etag, size FROM dav_cards WHERE addressbookid = $1 AND uri = $2", 1, uri, ).Scan(&cardData, &lastModified, &etag, &size) require.NoError(t, err) assert.Contains(t, cardData, "FN:Jane Doe") assert.Contains(t, cardData, "N:Doe;Jane;;;") assert.NotContains(t, cardData, "FN:Old Name") assert.Equal(t, len(cardData), size) assert.Equal(t, etag, fmt.Sprintf("%d", lastModified)) assert.GreaterOrEqual(t, lastModified, now, "lastmodified should be updated to current time or later") } func TestDeleteContact_Success(t *testing.T) { ctx := context.Background() userID := "test-delete-contact-" + t.Name() uri := userID + ".vcf" now := time.Now().Unix() _, err := testSvc.db.Exec(ctx, `INSERT INTO dav_cards (addressbookid, uri, carddata, lastmodified, etag, size) VALUES ($1, $2, $3, $4, $5, $6)`, 1, uri, "BEGIN:VCARD\nVERSION:3.0\nFN:Delete Me\nEND:VCARD", now, fmt.Sprintf("%d", now), 100, ) require.NoError(t, err) var count int err = testSvc.db.QueryRow(ctx, "SELECT COUNT(*) FROM dav_cards WHERE addressbookid = $1 AND uri = $2", 1, uri).Scan(&count) require.NoError(t, err) assert.Equal(t, 1, count, "contact should exist before deletion") err = testSvc.DeleteContact(1, uri) require.NoError(t, err) err = testSvc.db.QueryRow(ctx, "SELECT COUNT(*) FROM dav_cards WHERE addressbookid = $1 AND uri = $2", 1, uri).Scan(&count) require.NoError(t, err) assert.Equal(t, 0, count, "contact should be deleted") } func TestCreateEvent_Success(t *testing.T) { ctx := context.Background() now := time.Now() uniqueSummary := "TestCreateEvent-" + t.Name() input := EventInput{ Summary: uniqueSummary, Description: "Test description", Location: "Test location", Start: now.Add(24 * time.Hour), End: now.Add(25 * time.Hour), AllDay: false, } err := testSvc.CreateEvent(1, input) require.NoError(t, err) var uid, uri, calendarData string var firstOcc, lastOcc int64 var componentType string err = testSvc.db.QueryRow(ctx, "SELECT uid, uri, calendardata, firstoccurence, lastoccurence, componenttype FROM dav_calendarobjects WHERE calendarid = $1 AND calendardata LIKE $2", 1, "%SUMMARY:"+uniqueSummary+"%", ).Scan(&uid, &uri, &calendarData, &firstOcc, &lastOcc, &componentType) require.NoError(t, err, "created event should be queryable by summary in calendardata") t.Cleanup(func() { _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_calendarobjects WHERE uid = $1", uid) }) assert.NotEmpty(t, uid) assert.Equal(t, uid+".ics", uri) assert.Equal(t, "VEVENT", componentType) assert.Equal(t, input.Start.Unix(), firstOcc) assert.Equal(t, input.End.Unix(), lastOcc) assert.Contains(t, calendarData, "SUMMARY:"+uniqueSummary) assert.Contains(t, calendarData, "DESCRIPTION:Test description") assert.Contains(t, calendarData, "LOCATION:Test location") } func TestUpdateEvent_Success(t *testing.T) { ctx := context.Background() knownUID := "test-update-event-" + t.Name() uri := knownUID + ".ics" now := time.Now() start := now.Add(48 * time.Hour) end := now.Add(49 * time.Hour) calData := "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nUID:" + knownUID + "\nSUMMARY:Original\nEND:VEVENT\nEND:VCALENDAR" _, err := testSvc.db.Exec(ctx, `INSERT INTO dav_calendarobjects (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES ($1, $2, $3, $4, $5, $6, 'VEVENT', $7, $8, $9)`, 1, uri, calData, now.Unix(), fmt.Sprintf("%d", now.Unix()), len(calData), start.Unix(), end.Unix(), knownUID, ) require.NoError(t, err) t.Cleanup(func() { _, _ = testSvc.db.Exec(ctx, "DELETE FROM dav_calendarobjects WHERE uid = $1", knownUID) }) updatedInput := EventInput{ Summary: "Updated Event", Description: "Updated description", Location: "Updated location", Start: now.Add(72 * time.Hour), End: now.Add(73 * time.Hour), AllDay: false, } err = testSvc.UpdateEvent(1, knownUID, updatedInput) require.NoError(t, err) var calendarData string var firstOcc, lastOcc int64 err = testSvc.db.QueryRow(ctx, "SELECT calendardata, firstoccurence, lastoccurence FROM dav_calendarobjects WHERE uid = $1", knownUID, ).Scan(&calendarData, &firstOcc, &lastOcc) require.NoError(t, err) assert.Contains(t, calendarData, "SUMMARY:Updated Event") assert.Contains(t, calendarData, "DESCRIPTION:Updated description") assert.NotContains(t, calendarData, "SUMMARY:Original") assert.Equal(t, updatedInput.Start.Unix(), firstOcc, "firstoccurence should be updated") assert.Equal(t, updatedInput.End.Unix(), lastOcc, "lastoccurence should be updated") } func TestDeleteEvent_Success(t *testing.T) { ctx := context.Background() knownUID := "test-delete-event-" + t.Name() uri := knownUID + ".ics" now := time.Now() start := now.Add(48 * time.Hour) end := now.Add(49 * time.Hour) calData := "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nUID:" + knownUID + "\nSUMMARY:Delete Me\nEND:VEVENT\nEND:VCALENDAR" _, err := testSvc.db.Exec(ctx, `INSERT INTO dav_calendarobjects (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES ($1, $2, $3, $4, $5, $6, 'VEVENT', $7, $8, $9)`, 1, uri, calData, now.Unix(), fmt.Sprintf("%d", now.Unix()), len(calData), start.Unix(), end.Unix(), knownUID, ) require.NoError(t, err) var count int err = testSvc.db.QueryRow(ctx, "SELECT COUNT(*) FROM dav_calendarobjects WHERE uid = $1", knownUID).Scan(&count) require.NoError(t, err) assert.Equal(t, 1, count, "event should exist before deletion") err = testSvc.DeleteEvent(1, knownUID) require.NoError(t, err) err = testSvc.db.QueryRow(ctx, "SELECT COUNT(*) FROM dav_calendarobjects WHERE uid = $1", knownUID).Scan(&count) require.NoError(t, err) assert.Equal(t, 0, count, "event should be deleted") }