package service import ( "context" "testing" "time" "hyapp/pkg/appcode" "hyapp/services/room-service/internal/room/cell" "hyapp/services/room-service/internal/room/rank" "hyapp/services/room-service/internal/room/state" "hyapp/services/room-service/internal/router" ) func TestLoadedRoomLeaseDropsCellWhenLeaseTokenChanges(t *testing.T) { ctx := appcode.WithContext(context.Background(), "lalu") roomID := "room-token-gap" now := time.Date(2026, 7, 7, 9, 0, 0, 0, time.UTC) directory := router.NewMemoryDirectory() svc := &Service{ nodeID: "room-node-a", leaseTTL: time.Second, directory: directory, cells: make(map[string]*loadedCell), } lease, err := directory.EnsureOwner(ctx, runtimeRoomKey("lalu", roomID), svc.nodeID, now, svc.leaseTTL) if err != nil { t.Fatalf("ensure first lease: %v", err) } roomState := state.NewRoomState(roomID, 1001, 8, "voice") roomState.Version = 7 svc.installCell(ctx, roomID, cell.New(roomState, rank.FromState(roomState.GiftRank, 20)), lease.LeaseToken) if got := svc.loadCellForLease(ctx, roomID, lease); got == nil { t.Fatal("freshly installed cell should be reusable with the original lease token") } if _, owned, err := svc.loadedRoomLease(ctx, loadedRoomRef{AppCode: "lalu", RoomID: roomID}, now.Add(2*time.Second)); err != nil { t.Fatalf("load expired lease: %v", err) } else if owned { t.Fatal("expired lease re-acquisition must not keep using the old in-memory cell") } if got := svc.loadCell(ctx, roomID); got != nil { t.Fatal("old in-memory cell must be forgotten after a lease-token discontinuity") } currentLease, exists, err := directory.Lookup(ctx, runtimeRoomKey("lalu", roomID)) if err != nil { t.Fatalf("lookup current lease: %v", err) } if !exists || currentLease.LeaseToken == lease.LeaseToken { t.Fatal("test setup expected the directory to issue a new lease token after expiry") } } func TestLoadCellForCurrentOwnerRejectsReadOnlyAndExpiredCells(t *testing.T) { ctx := appcode.WithContext(context.Background(), "lalu") roomID := "room-read-current-owner" now := time.Date(2026, 7, 7, 10, 0, 0, 0, time.UTC) directory := router.NewMemoryDirectory() svc := &Service{ nodeID: "room-node-a", leaseTTL: time.Second, clock: fixedRoomTestClock{now: now}, directory: directory, cells: make(map[string]*loadedCell), } roomState := state.NewRoomState(roomID, 1001, 8, "voice") lease, err := directory.EnsureOwner(ctx, runtimeRoomKey("lalu", roomID), svc.nodeID, now, svc.leaseTTL) if err != nil { t.Fatalf("ensure lease: %v", err) } svc.installCell(ctx, roomID, cell.New(roomState.Clone(), rank.FromState(roomState.GiftRank, 20)), "") if got, err := svc.loadCellForCurrentOwner(ctx, roomID); err != nil { t.Fatalf("load read-only token cell: %v", err) } else if got != nil { t.Fatal("read-only recovered cell must not be reused as current-owner state") } if got := svc.loadCell(ctx, roomID); got != nil { t.Fatal("read-only cell should be forgotten after current-owner token check") } svc.installCell(ctx, roomID, cell.New(roomState.Clone(), rank.FromState(roomState.GiftRank, 20)), lease.LeaseToken) svc.clock = fixedRoomTestClock{now: now.Add(2 * time.Second)} if got, err := svc.loadCellForCurrentOwner(ctx, roomID); err != nil { t.Fatalf("load expired token cell: %v", err) } else if got != nil { t.Fatal("expired owner lease must not authorize reading the old in-memory cell") } } type fixedRoomTestClock struct { now time.Time } func (c fixedRoomTestClock) Now() time.Time { return c.now }