test(server): follow the signatures the session and port work changed

The session gate passes a session lookup to verifyJwtAndLoadUser, the
Immich save takes auto_upload, getAppUrl stays synchronous, the demo
session list marks the current session by the id it is handed, and the
lease no longer needs a transaction of its own, so its spec is gone.
This commit is contained in:
Maurice
2026-10-08 20:22:16 +02:00
parent 37af6f4ff9
commit da97d7535f
6 changed files with 27 additions and 34 deletions
@@ -95,7 +95,11 @@ describe('SessionsController on a demo instance', () => {
vi.stubEnv('DEMO_MODE', 'true');
const mine = { id: SID, created_at: 'a', last_seen_at: 'b', expires_at: 'c', user_agent: 'Mine', current: true };
const theirs = { id: OTHER, created_at: 'a', last_seen_at: 'b', expires_at: 'c', user_agent: 'Another visitor', current: false };
const { c } = controller({ list: vi.fn().mockResolvedValue([theirs, mine]) });
// Flags `current` from the id it is handed, as SessionsService.list does:
// a request without a tracked token has no current session at all.
const list = vi.fn((_userId: number, currentId?: string) =>
Promise.resolve([theirs, mine].map((session) => ({ ...session, current: session.id === currentId }))));
const { c } = controller({ list });
expect(await c.list(demo, reqWith(SID))).toEqual({ sessions: [mine], current_tracked: true });
expect(await c.list(demo, reqWith())).toEqual({ sessions: [], current_tracked: false });
@@ -335,31 +335,6 @@ describe('CronRegistrarService', () => {
expect(lines()[0]).toMatch(new RegExp(`^cron ${name} ok \\d+ms$`));
});
it('CRONREG-022: the lease is taken inside one transaction', async () => {
const name = 'lease-in-a-transaction';
const original = SchedulerLeasesRepository.prototype.acquire;
const inTransaction: boolean[] = [];
const spy = vi
.spyOn(SchedulerLeasesRepository.prototype, 'acquire')
.mockImplementation(function (this: SchedulerLeasesRepository, ...args: Parameters<typeof original>) {
inTransaction.push(t.orm.em.getContext().isInTransaction());
return original.apply(this, args);
});
try {
const registrar = new CronRegistrarService(new SchedulerRegistry(), { isTest: () => false } as RuntimeEnvService, t.orm);
let ran = false;
registrar.register(name, '* * * * *', () => {
ran = true;
});
await h.jobs[0].onTick();
expect(inTransaction).toEqual([true]);
expect(ran).toBe(true);
expect((await holder(name))!.owner).toBe(LEASE_OWNER);
} finally {
spy.mockRestore();
}
});
it('CRONREG-018: a lapsed lease is taken over, and held for the settle window after the tick', async () => {
const name = 'lease-lapsed';
await insertRow(t, SchedulerLeases, { name, owner: 'crashed-host:9:gone', expires_at: Date.now() - 1 });
+7 -3
View File
@@ -78,6 +78,7 @@ import { RealtimeService } from '../../../src/nest/realtime/realtime.service';
import { EphemeralTokenService } from '../../../src/nest/auth/ephemeral-token.service';
import type { EntityManager } from '@mikro-orm/core';
import { Users } from '../../../src/db/entities/Users.entity';
import { UserSessions } from '../../../src/db/entities/UserSessions.entity';
const storageDelete = vi.fn();
const storageStub = { delete: storageDelete } as unknown as import('../../../src/nest/storage/storage.service').StorageService;
@@ -814,16 +815,19 @@ describe('authenticateDownload', () => {
// suite's stand-in for UsersRepository), not merely "a repository".
// `getRepository` itself is a spy (task-1-rereview.md F-R4), so this ALSO
// pins which entity class was asked for: the deep-equal alone would still
// hold if the production code called `em.getRepository(Trips)`.
expect(verifyJwtAndLoadUser).toHaveBeenCalledWith('cookie-jwt', {});
// hold if the production code called `em.getRepository(Trips)`. The third
// argument is the session lookup the session gate reads (UserSessions).
expect(verifyJwtAndLoadUser).toHaveBeenCalledWith('cookie-jwt', {}, {});
expect(getRepository).toHaveBeenCalledWith(Users);
expect(getRepository).toHaveBeenCalledWith(UserSessions);
});
it('FILE-SVC-033: a bearer token is used when no cookie is present; invalid JWTs 401', async () => {
verifyJwtAndLoadUser.mockReturnValue({ id: 7 });
expect(await svc.authenticateDownload(req({ bearer: 'bearer-jwt' }))).toEqual({ userId: 7 });
expect(verifyJwtAndLoadUser).toHaveBeenCalledWith('bearer-jwt', {}); // see FILE-SVC-032's comment
expect(verifyJwtAndLoadUser).toHaveBeenCalledWith('bearer-jwt', {}, {}); // see FILE-SVC-032's comment
expect(getRepository).toHaveBeenCalledWith(Users);
expect(getRepository).toHaveBeenCalledWith(UserSessions);
verifyJwtAndLoadUser.mockReturnValue(null);
expect(await svc.authenticateDownload(req({ bearer: 'stale' }))).toEqual({ error: 'Invalid or expired token', status: 401 });
@@ -143,7 +143,7 @@ describe('OidcController /login', () => {
it('trims a trailing slash off APP_URL when building the redirect uri', async () => {
const res = makeRes();
const createState = vi.fn().mockResolvedValue({ state: 'st', codeChallenge: 'cc' });
await ctl(svc({ getAppUrl: vi.fn().mockResolvedValue('https://app///'), createState })).login(req, res);
await ctl(svc({ getAppUrl: vi.fn().mockReturnValue('https://app///'), createState })).login(req, res);
expect(createState).toHaveBeenCalledWith('https://app/api/auth/oidc/callback', undefined, undefined);
});
@@ -185,7 +185,12 @@ describe('applyPlatformUploads', () => {
res,
next,
);
expect(h.verifyJwtAndLoadUser).toHaveBeenCalledWith('jwt123', expect.objectContaining({ findByIdWithPasswordVersion: expect.any(Function) }));
expect(h.verifyJwtAndLoadUser).toHaveBeenCalledWith(
'jwt123',
expect.objectContaining({ findByIdWithPasswordVersion: expect.any(Function) }),
// the session gate's lookup (UserSessionsRepository)
expect.objectContaining({ findActive: expect.any(Function), touchLastSeen: expect.any(Function) }),
);
expect(h.sendToResponse).toHaveBeenCalledWith('photos', 'a.jpg', res);
});
@@ -195,7 +200,12 @@ describe('applyPlatformUploads', () => {
h.verifyJwtAndLoadUser.mockReturnValue({ id: 1 });
const res = makeRes();
await photoHandler()({ params: { filename: 'a.jpg' }, headers: {}, query: { token: 'qtok' } }, res, next);
expect(h.verifyJwtAndLoadUser).toHaveBeenCalledWith('qtok', expect.objectContaining({ findByIdWithPasswordVersion: expect.any(Function) }));
expect(h.verifyJwtAndLoadUser).toHaveBeenCalledWith(
'qtok',
expect.objectContaining({ findByIdWithPasswordVersion: expect.any(Function) }),
// the session gate's lookup (UserSessionsRepository)
expect.objectContaining({ findActive: expect.any(Function), touchLastSeen: expect.any(Function) }),
);
expect(h.sendToResponse).toHaveBeenCalledWith('photos', 'a.jpg', res);
});