import { describe, expect, it } from 'bun:test'; import { plannedOutcome, servicesNeedingMemberProvision } from './members'; import { byId, type CatalogueEntry } from './catalogue'; // `members: 'invite'` has no catalogue entry as of 2026-08-18 — the vault was the only one, and it left // to become `plugins/vault/`. The BRANCH is still live code any future entry can reach, so it is tested // against a synthetic entry rather than dropped: deleting the assertion with the last real user of a // case is how a switch arm stops being covered without anyone deciding that it should. const inviteOnly = { members: 'invite' } as CatalogueEntry; describe('what adding a member would do', () => { it('predicts the outcome per service without contacting anything', () => { expect(plannedOutcome(byId('memos')!)).toBe('provisioned'); expect(plannedOutcome(inviteOnly)).toBe('invited'); expect(plannedOutcome(byId('vnc')!)).toBe('not-applicable'); }); }); describe('which installed services have work to do', () => { it('skips single-tenant daemons entirely', () => { // memos provisions accounts; vnc has no user concept and must be skipped. // // Down to ONE `accounts` service in the catalogue, which is why this names a single id rather than // two. It has been re-pointed five times as services left — jellyfin, invoiceshelf and slskd to // `plugins/`, caldav to CORE — and memos is the last one standing. If memos ever leaves too, this // wants a literal fixture like `installer.test.ts` has, not a sixth re-point. const ids = servicesNeedingMemberProvision(['memos', 'vnc']).map((e) => e.id); expect(ids.sort()).toEqual(['memos']); }); it('ignores services that are not installed', () => { // The catalogue is what COULD be installed; only what IS installed has a member to provision. expect(servicesNeedingMemberProvision([])).toEqual([]); expect(servicesNeedingMemberProvision(['memos']).map((e) => e.id)).toEqual(['memos']); }); });