test: add coverage tests across backend + fix mock for PENDING checkout support
CI / Nginx config check (push) Successful in 13s
CI / Env docs check (push) Successful in 15s
CI / Docker compose check (push) Successful in 15s
CI / Frontend major deps (push) Failing after 24s
CI / Frontend deps check (push) Successful in 30s
CI / Secrets scan (push) Successful in 38s
CI / Go build (push) Successful in 39s
CI / Frontend build (push) Successful in 1m3s
CI / Knip (push) Successful in 45s
CI / Go vet (prod) (push) Failing after 1m42s
CI / Frontend a11y check (push) Successful in 2m34s
CI / Go vet (dev) (push) Successful in 2m29s
CI / Staticcheck (prod) (push) Failing after 2m38s
CI / go mod tidy (push) Successful in 1m3s
CI / Staticcheck (dev) (push) Successful in 2m55s
CI / Frontend QC (audit) (push) Successful in 51s
CI / golangci-lint (push) Successful in 3m22s
CI / Go vulnerabilities (push) Successful in 1m26s
CI / Frontend QC (typecheck) (push) Successful in 2m18s
CI / Security scan (prod) (push) Successful in 4m18s
CI / Security scan (dev) (push) Successful in 4m40s
CI / Tests (prod) (push) Has been skipped
CI / Tests (dev) (push) Has been skipped
CI / Race (prod) (push) Has been skipped
CI / Race (dev) (push) Has been skipped
CI / Frontend QC (lint) (push) Successful in 2m18s
CI / Svelte strict check (push) Successful in 43s
CI / Nginx config check (push) Successful in 13s
CI / Env docs check (push) Successful in 15s
CI / Docker compose check (push) Successful in 15s
CI / Frontend major deps (push) Failing after 24s
CI / Frontend deps check (push) Successful in 30s
CI / Secrets scan (push) Successful in 38s
CI / Go build (push) Successful in 39s
CI / Frontend build (push) Successful in 1m3s
CI / Knip (push) Successful in 45s
CI / Go vet (prod) (push) Failing after 1m42s
CI / Frontend a11y check (push) Successful in 2m34s
CI / Go vet (dev) (push) Successful in 2m29s
CI / Staticcheck (prod) (push) Failing after 2m38s
CI / go mod tidy (push) Successful in 1m3s
CI / Staticcheck (dev) (push) Successful in 2m55s
CI / Frontend QC (audit) (push) Successful in 51s
CI / golangci-lint (push) Successful in 3m22s
CI / Go vulnerabilities (push) Successful in 1m26s
CI / Frontend QC (typecheck) (push) Successful in 2m18s
CI / Security scan (prod) (push) Successful in 4m18s
CI / Security scan (dev) (push) Successful in 4m40s
CI / Tests (prod) (push) Has been skipped
CI / Tests (dev) (push) Has been skipped
CI / Race (prod) (push) Has been skipped
CI / Race (dev) (push) Has been skipped
CI / Frontend QC (lint) (push) Successful in 2m18s
CI / Svelte strict check (push) Successful in 43s
New test files cover previously untested paths across DAV, validators, S3, Square, mw, bookings, user, and payments packages. Includes mock fix: HoldCheckouts flag on MockClient allows tests to pause auto-complete goroutine for testing PENDING checkout states. Coverage: 50.4% → 65.0% (+14.6pp)
This commit is contained in:
@@ -0,0 +1,655 @@
|
||||
//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")
|
||||
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")
|
||||
}
|
||||
Reference in New Issue
Block a user