// §22.8 — the request-to-join API client builds scope-keyed URLs and methods. import { describe, it, expect, vi, afterEach } from 'vitest' import { joinTarget, requestJoin, acceptJoinRequest, declineJoinRequest } from './api.js' function mockFetch() { const fn = vi.fn(async () => ({ ok: true, status: 200, json: async () => ({ ok: true }), })) global.fetch = fn return fn } afterEach(() => { vi.restoreAllMocks() }) describe('join-request api URLs', () => { it('joinTarget GETs the scope join-target', async () => { const f = mockFetch() await joinTarget('collection', 'features') expect(f).toHaveBeenCalledWith('/api/scopes/collection/features/join-target') }) it('requestJoin POSTs the desired role + message', async () => { const f = mockFetch() await requestJoin('project', 'ohm', { role: 'contributor', message: 'hi' }) expect(f.mock.calls[0][0]).toBe('/api/scopes/project/ohm/join-requests') const opts = f.mock.calls[0][1] expect(opts.method).toBe('POST') expect(JSON.parse(opts.body)).toEqual({ role: 'contributor', message: 'hi' }) }) it('acceptJoinRequest POSTs the accept route with an optional role override', async () => { const f = mockFetch() await acceptJoinRequest('collection', 'features', 7, 'contributor') expect(f.mock.calls[0][0]).toBe('/api/scopes/collection/features/join-requests/7/accept') expect(JSON.parse(f.mock.calls[0][1].body)).toEqual({ role: 'contributor' }) }) it('declineJoinRequest POSTs the decline route', async () => { const f = mockFetch() await declineJoinRequest('collection', 'features', 7) expect(f.mock.calls[0][0]).toBe('/api/scopes/collection/features/join-requests/7/decline') expect(f.mock.calls[0][1].method).toBe('POST') }) })