package store_test import ( "context" "testing" "github.com/wangjia/pangolin/server/internal/devices" "github.com/wangjia/pangolin/server/internal/sessions" ) type fakeJTIRevoker struct{ revoked []string } func (f *fakeJTIRevoker) Revoke(_ context.Context, jti string) error { f.revoked = append(f.revoked, jti) return nil } func TestSQLite_ForceLogoutAndDelete(t *testing.T) { ctx := context.Background() db := openSQLite(t) if _, err := db.Exec(`INSERT INTO users (id, uuid, email, pw_hash, dp_uuid, status) VALUES (1,'u','u@e.com','h','dp','active')`); err != nil { t.Fatalf("seed user: %v", err) } if _, err := db.Exec(`INSERT INTO devices (id, uuid, user_id, name, platform, dp_uuid) VALUES (10,'dev-uuid',1,'Mac','macos','dp-dev')`); err != nil { t.Fatalf("seed device: %v", err) } ss := sessions.NewStore(db) ss.Create(ctx, 1, 10, "jti-a", "", "") ss.Create(ctx, 1, 10, "jti-b", "", "") jr := &fakeJTIRevoker{} cr := &devices.NoopRevoker{} svc := devices.NewService(devices.NewStore(db), cr) svc.SetSessionPort(ss) svc.SetJTIRevoker(jr) // Force-logout: sessions revoked, JTIs dropped, device kept. if e := svc.ForceLogout(ctx, 1, "dev-uuid"); e != nil { t.Fatalf("ForceLogout: %v", e) } var active int db.QueryRow(`SELECT COUNT(*) FROM sessions WHERE device_id=10 AND revoked_at IS NULL`).Scan(&active) if active != 0 { t.Fatalf("force-logout left %d active sessions", active) } if len(jr.revoked) != 2 { t.Fatalf("expected 2 JTIs revoked, got %v", jr.revoked) } if list, _ := svc.ListDevices(ctx, 1); len(list) != 1 { t.Fatalf("device should remain after force-logout, got %d", len(list)) } // Ownership / existence guards. if e := svc.ForceLogout(ctx, 2, "dev-uuid"); e == nil { t.Fatalf("expected forbidden for other user") } if e := svc.ForceLogout(ctx, 1, "nope"); e == nil { t.Fatalf("expected not found for unknown device") } // Clear-login (delete): device gone + per-device credential revoked by dp_uuid. if e := svc.DeleteDevice(ctx, 1, "dev-uuid"); e != nil { t.Fatalf("DeleteDevice: %v", e) } if list, _ := svc.ListDevices(ctx, 1); len(list) != 0 { t.Fatalf("device should be gone, got %d", len(list)) } if len(cr.Calls) != 1 || cr.Calls[0] != "dp-dev" { t.Fatalf("expected dp-dev credential revoke, got %v", cr.Calls) } }