import { describe, it, expect } from 'vitest'; import { ProvisionUsernameCollisionError, buildProvisionPlan, buildProvisionUsernameCandidates, shortHomeUserId, shouldAdoptCanonicalSecret } from './signal-server-provision.rules'; const HOME_USER_ID = 'a3f2b1c4-5678-90ab-cdef-1234567890ab'; function plan(canonical: string | null, deviceLocal: string | null) { return buildProvisionPlan({ preferredUsername: 'alice', homeUserId: HOME_USER_ID, secrets: { canonical, deviceLocal } }).map((attempt) => `${attempt.kind}:${attempt.username}:${attempt.secretSource}`); } describe('signal-server-provision.rules', () => { it('derives a stable short id from a home user uuid', () => { expect(shortHomeUserId('a3f2b1c4-5678-90ab-cdef-1234567890ab')).toBe('a3f2b1'); }); it('orders username candidates with preferred first then suffixed fallback', () => { expect( buildProvisionUsernameCandidates('alice', 'a3f2b1c4-5678-90ab-cdef-1234567890ab') ).toEqual(['alice', 'alice-a3f2b1']); }); it('deduplicates candidates when suffix would repeat preferred username', () => { expect( buildProvisionUsernameCandidates('alice-a3f2b1', 'a3f2b1c4-5678-90ab-cdef-1234567890ab') ).toEqual(['alice-a3f2b1']); }); it('signs in to the preferred username before ever trying the suffixed one', () => { expect(plan('canonical-secret', null)).toEqual([ 'register:alice:canonical', 'login:alice:canonical', 'register:alice-a3f2b1:canonical', 'login:alice-a3f2b1:canonical' ]); }); it('tries the legacy device secret before giving the username up as someone else\'s', () => { expect(plan('canonical-secret', 'legacy-secret')).toEqual([ 'register:alice:canonical', 'login:alice:canonical', 'login:alice:device-local', 'register:alice-a3f2b1:canonical', 'login:alice-a3f2b1:canonical', 'login:alice-a3f2b1:device-local' ]); }); it('never registers a suffixed duplicate without a canonical secret', () => { expect(plan(null, 'legacy-secret')).toEqual([ 'register:alice:device-local', 'login:alice:device-local', 'login:alice-a3f2b1:device-local' ]); }); it('produces no attempts when no secret is available at all', () => { expect(plan(null, null)).toEqual([]); }); it('does not repeat the canonical secret as a legacy attempt', () => { expect(plan('same-secret', 'same-secret')).toEqual([ 'register:alice:canonical', 'login:alice:canonical', 'register:alice-a3f2b1:canonical', 'login:alice-a3f2b1:canonical' ]); }); it('adopts the canonical secret only after a legacy login', () => { expect(shouldAdoptCanonicalSecret({ kind: 'login', secretSource: 'device-local' }, 'canonical')).toBe(true); expect(shouldAdoptCanonicalSecret({ kind: 'login', secretSource: 'canonical' }, 'canonical')).toBe(false); expect(shouldAdoptCanonicalSecret({ kind: 'register', secretSource: 'device-local' }, 'canonical')).toBe(false); expect(shouldAdoptCanonicalSecret({ kind: 'login', secretSource: 'device-local' }, null)).toBe(false); }); it('exposes attempted usernames on collision errors', () => { const error = new ProvisionUsernameCollisionError('https://signal.example.com', ['alice', 'alice-a3f2b1']); expect(error.name).toBe('ProvisionUsernameCollisionError'); expect(error.serverUrl).toBe('https://signal.example.com'); expect(error.attemptedUsernames).toEqual(['alice', 'alice-a3f2b1']); }); });