ci / verify (push) Canceled after 0s
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
117 lines
4.7 KiB
Go
117 lines
4.7 KiB
Go
package webevents
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"data-terminal-cloud/internal/identity"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
func TestServiceCursorIsolationCleanupAndPresenceExpiry(t *testing.T) {
|
|
databaseURL := os.Getenv("TEST_DATABASE_URL")
|
|
if databaseURL == "" {
|
|
t.Skip("TEST_DATABASE_URL is not set")
|
|
}
|
|
ctx := context.Background()
|
|
pool, err := pgxpool.New(ctx, databaseURL)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer pool.Close()
|
|
service := NewService(pool)
|
|
owner := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000101"}
|
|
outsider := identity.Principal{Issuer: owner.Issuer, Subject: "00000000-0000-0000-0000-000000000103"}
|
|
outsideBaseline, err := service.StartingCursor(ctx, outsider)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resourceIDs := []string{uuid.NewString(), uuid.NewString(), uuid.NewString()}
|
|
testStarted := time.Now().UTC().Add(-time.Second)
|
|
defer func() {
|
|
_, _ = pool.Exec(ctx, `DELETE FROM device_presence_state WHERE tenant_id='10000000-0000-0000-0000-000000000001' AND device_id='10000000-0000-0000-0000-000000000501'`)
|
|
_, _ = pool.Exec(ctx, `DELETE FROM web_event WHERE resource_public_id=ANY($1::uuid[]) OR (resource_public_id='10000000-0000-0000-0000-000000000511' AND occurred_at>=$2)`, resourceIDs, testStarted)
|
|
}()
|
|
ids := make([]int64, 3)
|
|
for index, resourceID := range resourceIDs {
|
|
expiresAt := time.Now().UTC().Add(10 * time.Minute)
|
|
if index == 2 {
|
|
expiresAt = time.Now().UTC().Add(-time.Minute)
|
|
}
|
|
if err := pool.QueryRow(ctx, `
|
|
INSERT INTO web_event(tenant_id,household_id,subject_id,event_type,resource_type,resource_public_id,status,expires_at)
|
|
VALUES('10000000-0000-0000-0000-000000000001','10000000-0000-0000-0000-000000000201','10000000-0000-0000-0000-000000000401',
|
|
'conversation.completed','conversation',$1::uuid,$2,$3) RETURNING id`, resourceID, "TEST_"+string(rune('A'+index)), expiresAt).Scan(&ids[index]); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
cursor, err := service.StartingCursor(ctx, owner)
|
|
if err != nil || cursor != ids[1] {
|
|
t.Fatalf("owner starting cursor=%d want=%d err=%v", cursor, ids[1], err)
|
|
}
|
|
outsideCursor, err := service.StartingCursor(ctx, outsider)
|
|
if err != nil || outsideCursor != outsideBaseline {
|
|
t.Fatalf("outsider starting cursor=%d err=%v", outsideCursor, err)
|
|
}
|
|
if err := service.ValidateCursor(ctx, owner, 0); err != nil {
|
|
t.Fatalf("zero cursor: %v", err)
|
|
}
|
|
if err := service.ValidateCursor(ctx, owner, ids[0]); err != nil {
|
|
t.Fatalf("valid cursor: %v", err)
|
|
}
|
|
if err := service.ValidateCursor(ctx, owner, ids[2]); !errors.Is(err, ErrCursorUnavailable) {
|
|
t.Fatalf("expired cursor error=%v", err)
|
|
}
|
|
if err := service.ValidateCursor(ctx, outsider, ids[0]); !errors.Is(err, ErrCursorUnavailable) {
|
|
t.Fatalf("cross-user cursor error=%v", err)
|
|
}
|
|
items, err := service.ListAfter(ctx, owner, ids[0], 1)
|
|
if err != nil || len(items) != 1 || items[0].ID != ids[1] || items[0].HouseholdID != "10000000-0000-0000-0000-000000000211" || items[0].SubjectID != "10000000-0000-0000-0000-000000000411" {
|
|
t.Fatalf("events=%+v err=%v", items, err)
|
|
}
|
|
allItems, err := service.ListAfter(ctx, owner, 0, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
found := 0
|
|
for _, item := range allItems {
|
|
if item.ID == ids[0] || item.ID == ids[1] {
|
|
found++
|
|
}
|
|
}
|
|
if found != 2 {
|
|
t.Fatalf("active synthetic events found=%d", found)
|
|
}
|
|
outsideItems, err := service.ListAfter(ctx, outsider, outsideBaseline, 100)
|
|
if err != nil || len(outsideItems) != 0 {
|
|
t.Fatalf("outsider events=%+v err=%v", outsideItems, err)
|
|
}
|
|
deleted, err := service.DeleteExpired(ctx, 1)
|
|
if err != nil || deleted != 1 {
|
|
t.Fatalf("deleted expired events=%d err=%v", deleted, err)
|
|
}
|
|
if err := service.ValidateCursor(ctx, owner, ids[2]); !errors.Is(err, ErrCursorUnavailable) {
|
|
t.Fatalf("deleted cursor error=%v", err)
|
|
}
|
|
|
|
if _, err := pool.Exec(ctx, `
|
|
INSERT INTO device_presence_state(tenant_id,device_id,household_id,subject_id,online,last_seen_at,changed_at)
|
|
VALUES('10000000-0000-0000-0000-000000000001','10000000-0000-0000-0000-000000000501','10000000-0000-0000-0000-000000000201',
|
|
'10000000-0000-0000-0000-000000000401',true,now()-interval '10 minutes',now()-interval '10 minutes')`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
expired, err := service.ExpireDevicePresence(ctx, time.Now().UTC().Add(-5*time.Minute), 0)
|
|
if err != nil || expired != 1 {
|
|
t.Fatalf("expired device presence=%d err=%v", expired, err)
|
|
}
|
|
var online bool
|
|
if err := pool.QueryRow(ctx, `SELECT online FROM device_presence_state WHERE tenant_id='10000000-0000-0000-0000-000000000001' AND device_id='10000000-0000-0000-0000-000000000501'`).Scan(&online); err != nil || online {
|
|
t.Fatalf("device presence online=%v err=%v", online, err)
|
|
}
|
|
}
|