import path from 'node:path'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as auth from '../src/agent-auth.js'; import * as storage from '../src/agent-credentials.js'; import * as deployment from '../src/agent-deployment.js'; import * as config from '../src/config.js'; import { prepareAgentDeployment, formatDeployment } from '../src/deploy.js'; vi.mock('../src/agent-auth.js'); vi.mock('../src/agent-credentials.js'); vi.mock('../src/agent-deployment.js'); vi.mock('../src/config.js'); const credentials = { registrationId: 'reg_1', issuer: 'https://auth.example.com', assertion: 'assertion', claimToken: 'claim', }; const site = { status: 'ready' as const, created: true, deploymentId: 'id', subdomain: 'allocated', remote: 'https://git.example.com/repo', branch: 'main' as const, expiresAt: '2026-12-01T00:00:00.000Z', }; const write = { remote: site.remote, branch: site.branch, token: 'write', tokenExpiresAt: site.expiresAt, }; const options = { apiUrl: 'https://api.example.com', directory: path.resolve('docs'), subdomain: 'requested', }; const warn = vi.fn(); beforeEach(() => { vi.resetAllMocks(); vi.mocked(storage.loadAgentCredentials).mockResolvedValue(null); vi.mocked(storage.saveAgentCredentials).mockResolvedValue(undefined); vi.mocked(auth.registerAgent).mockResolvedValue(credentials); vi.mocked(auth.exchangeAgentToken).mockImplementation(async (_, value) => ({ ...value, accessToken: 'access', })); vi.mocked(auth.getAgentIdentity).mockResolvedValue({ registrationId: 'reg_1', identityType: 'anonymous', }); vi.mocked(deployment.provisionAgentSite).mockResolvedValue(site); vi.mocked(deployment.getAgentWriteToken).mockResolvedValue(write); vi.mocked(config.setAgentSiteMetadata).mockResolvedValue(undefined); }); describe('mint deploy', () => { it.each([ ['https://leaves.mintlify.com', 'https://allocated.mintlify.site/'], ['https://leaves-dev.mintlify.com', 'https://allocated.mint-dev.mintlify.me/'], ])('prints the site URL for %s', async (apiUrl, siteUrl) => { const result = await prepareAgentDeployment({ ...options, apiUrl }, warn); expect(formatDeployment(result, apiUrl)).toContain(`Site: ${siteUrl}`); }); it('continues provisioning after keyring save failure', async () => { vi.mocked(storage.saveAgentCredentials).mockRejectedValue(new Error('locked')); const result = await prepareAgentDeployment(options, warn); expect(result.status).toBe('ready'); expect(warn).toHaveBeenCalledWith(expect.stringContaining('Continuing for this run')); expect(formatDeployment(result, options.apiUrl)).toContain('https://allocated.mintlify.site'); }); it('persists the exchanged access token for later commands', async () => { await prepareAgentDeployment(options, warn); expect(storage.saveAgentCredentials).toHaveBeenLastCalledWith( options.apiUrl, options.directory, { ...credentials, accessToken: 'access' } ); }); it('reuses the current registration and the saved site name', async () => { vi.mocked(storage.loadAgentCredentials).mockResolvedValue(credentials); vi.mocked(config.getAgentSiteMetadata).mockReturnValue({ registrationId: 'reg_1', subdomain: 'allocated', }); await prepareAgentDeployment({ ...options, subdomain: undefined }, warn); expect(auth.registerAgent).not.toHaveBeenCalled(); expect(deployment.provisionAgentSite).toHaveBeenCalledWith(options.apiUrl, 'access', { subdomain: 'allocated', orgName: undefined, }); }); it('starts a new registration only when explicitly requested or the site expired', async () => { vi.mocked(storage.loadAgentCredentials).mockResolvedValue(credentials); await prepareAgentDeployment({ ...options, newSite: true }, warn); expect(storage.loadAgentCredentials).not.toHaveBeenCalled(); expect(auth.registerAgent).toHaveBeenCalledTimes(1); }); it('requires an explicit site name for --new even when a previous name is saved', async () => { vi.mocked(config.getAgentSiteMetadata).mockReturnValue({ registrationId: 'reg_1', subdomain: 'allocated', }); await expect( prepareAgentDeployment({ ...options, newSite: true, subdomain: undefined }, warn) ).rejects.toThrow('Choose a site name with --subdomain'); expect(auth.discoverAgentAuth).not.toHaveBeenCalled(); expect(auth.registerAgent).not.toHaveBeenCalled(); expect(storage.saveAgentCredentials).not.toHaveBeenCalled(); expect(deployment.provisionAgentSite).not.toHaveBeenCalled(); }); it('starts again on confirmed deployment expiry', async () => { vi.mocked(storage.loadAgentCredentials).mockResolvedValue(credentials); vi.mocked(deployment.provisionAgentSite).mockResolvedValueOnce({ status: 'expired', created: false, code: 'deployment_already_exists', deploymentId: 'old', subdomain: 'old', }); await prepareAgentDeployment(options, warn); expect(auth.registerAgent).toHaveBeenCalledTimes(1); expect(deployment.provisionAgentSite).toHaveBeenCalledTimes(2); }); it('does not provision for a claimed identity', async () => { vi.mocked(auth.getAgentIdentity).mockResolvedValue({ registrationId: 'reg_1', identityType: 'user_bound', userId: 'user', orgId: 'org', }); const result = await prepareAgentDeployment(options, warn); expect(formatDeployment(result, options.apiUrl)).toContain('--new'); expect(deployment.provisionAgentSite).not.toHaveBeenCalled(); }); it('does not replace an existing identity after an authentication outage', async () => { vi.mocked(storage.loadAgentCredentials).mockResolvedValue(credentials); vi.mocked(auth.getAgentIdentity).mockRejectedValue(new Error('unavailable')); await expect(prepareAgentDeployment(options, warn)).rejects.toThrow('unavailable'); expect(auth.registerAgent).not.toHaveBeenCalled(); }); it('does not create a site after failing to read known credentials', async () => { vi.mocked(config.getAgentSiteMetadata).mockReturnValue({ registrationId: 'reg_1', subdomain: 'allocated', }); vi.mocked(storage.loadAgentCredentials).mockRejectedValue(new Error('locked')); await expect(prepareAgentDeployment(options, warn)).rejects.toThrow('Could not read'); expect(auth.registerAgent).not.toHaveBeenCalled(); }); });