2026-08-26 06:41:07 +08:00
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" }
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 != 0 {
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 )
2026-08-30 21:22:21 +08:00
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" {
2026-08-26 06:41:07 +08:00
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 , 0 , 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 )
}
}