Files
bookorbit/server/src/modules/server-identity/server-identity.service.test.ts
T
neon fbaf0b63df feat(server): publish a stable installation identity for client connections
The iOS app lets an account reach one server through several addresses, such as a home network
IP and a public name. Before it sends credentials to an alternative address it needs to know the
address leads to the same installation, so expose a public, credential-free GET /server-identity
returning a persisted UUID, and add the same optional instanceId to app-info with the
server-connection-identity feature flag.

The UUID lives in a singleton server_identity row shared by every replica, created on first use
and cached per process. An unreadable identity omits instanceId instead of failing app-info,
which clients rely on for feature detection. Existing routes and bodies are unchanged and the
web client does not call the new route; the iOS app does.

The migration uses IF NOT EXISTS because an unreleased build created the same table as 0109.
2026-10-13 19:37:52 -06:00

22 lines
1.2 KiB
TypeScript

import { ServerIdentityRepository } from './server-identity.repository';
import { ServerIdentityService } from './server-identity.service';
describe('ServerIdentityService', () => {
it('shares the durable identity across concurrent requests', async () => {
const getOrCreate = vi.fn().mockResolvedValue('538a27ca-87ef-47bc-a195-13c8597c3529');
const service = new ServerIdentityService({ getOrCreate } as unknown as ServerIdentityRepository);
const [first, second] = await Promise.all([service.getIdentity(), service.getIdentity()]);
expect(first).toEqual({ product: 'bookorbit', protocolVersion: 1, instanceId: '538a27ca-87ef-47bc-a195-13c8597c3529' });
expect(second).toEqual(first);
expect(getOrCreate).toHaveBeenCalledOnce();
});
it('retries after a failed database read', async () => {
const getOrCreate = vi.fn().mockRejectedValueOnce(new Error('unavailable')).mockResolvedValue('restored');
const service = new ServerIdentityService({ getOrCreate } as unknown as ServerIdentityRepository);
await expect(service.getIdentity()).rejects.toThrow('unavailable');
expect((await service.getIdentity()).instanceId).toBe('restored');
expect(getOrCreate).toHaveBeenCalledTimes(2);
});
});