UNPKG

@redocly/cli

Version:

[@Redocly](https://redocly.com) CLI is your all-in-one OpenAPI utility. It builds, manages, improves, and quality-checks your OpenAPI descriptions, all of which comes in handy for various phases of the API Lifecycle. Create your own rulesets to make API g

338 lines (337 loc) 15.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const colorette_1 = require("colorette"); const api_client_1 = require("../api-client"); const originalFetch = global.fetch; beforeAll(() => { // Reset fetch mock before each test global.fetch = jest.fn(); }); afterAll(() => { // Restore original fetch after each test global.fetch = originalFetch; }); function mockFetchResponse(response) { global.fetch.mockResolvedValue(response); } describe('ApiClient', () => { const testToken = 'test-token'; const testDomain = 'test-domain.com'; const testOrg = 'test-org'; const testProject = 'test-project'; const version = '1.0.0'; const command = 'push'; const expectedUserAgent = `redocly-cli/${version} ${command}`; describe('getDefaultBranch()', () => { let apiClient; beforeEach(() => { apiClient = new api_client_1.ReuniteApi({ domain: testDomain, apiKey: testToken, version, command }); }); it('should get default project branch', async () => { mockFetchResponse({ ok: true, json: jest.fn().mockResolvedValue({ branchName: 'test-branch', }), }); const result = await apiClient.remotes.getDefaultBranch(testOrg, testProject); expect(global.fetch).toHaveBeenCalledWith(`${testDomain}/api/orgs/${testOrg}/projects/${testProject}/source`, { method: 'GET', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${testToken}`, 'user-agent': expectedUserAgent, }, signal: expect.any(Object), }); expect(result).toEqual('test-branch'); }); it('should throw parsed error if response is not ok', async () => { mockFetchResponse({ ok: false, json: jest.fn().mockResolvedValue({ type: 'about:blank', title: 'Project source not found', status: 404, detail: 'Not Found', object: 'problem', }), }); await expect(apiClient.remotes.getDefaultBranch(testOrg, testProject)).rejects.toThrow(new api_client_1.ReuniteApiError('Failed to fetch default branch. Project source not found.', 404)); }); it('should throw statusText error if response is not ok', async () => { mockFetchResponse({ ok: false, statusText: 'Not found', json: jest.fn().mockResolvedValue({ unknownField: 'unknown-error', }), }); await expect(apiClient.remotes.getDefaultBranch(testOrg, testProject)).rejects.toThrow(new api_client_1.ReuniteApiError('Failed to fetch default branch. Not found.', 404)); }); }); describe('upsert()', () => { const remotePayload = { mountBranchName: 'remote-mount-branch-name', mountPath: 'remote-mount-path', }; const responseMock = { id: 'remote-id', type: 'CICD', mountPath: 'remote-mount-path', mountBranchName: 'remote-mount-branch-name', organizationId: testOrg, projectId: testProject, }; let apiClient; beforeEach(() => { apiClient = new api_client_1.ReuniteApi({ domain: testDomain, apiKey: testToken, version, command }); }); it('should upsert remote', async () => { mockFetchResponse({ ok: true, json: jest.fn().mockResolvedValue(responseMock), }); const result = await apiClient.remotes.upsert(testOrg, testProject, remotePayload); expect(global.fetch).toHaveBeenCalledWith(`${testDomain}/api/orgs/${testOrg}/projects/${testProject}/remotes`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${testToken}`, 'user-agent': expectedUserAgent, }, body: JSON.stringify({ mountPath: remotePayload.mountPath, mountBranchName: remotePayload.mountBranchName, type: 'CICD', autoMerge: true, }), signal: expect.any(Object), agent: undefined, }); expect(result).toEqual(responseMock); }); it('should throw parsed error if response is not ok', async () => { mockFetchResponse({ ok: false, json: jest.fn().mockResolvedValue({ type: 'about:blank', title: 'Not allowed to mount remote outside of project content path: /docs', status: 403, detail: 'Forbidden', object: 'problem', }), }); await expect(apiClient.remotes.upsert(testOrg, testProject, remotePayload)).rejects.toThrow(new api_client_1.ReuniteApiError('Failed to upsert remote. Not allowed to mount remote outside of project content path: /docs.', 403)); }); it('should throw statusText error if response is not ok', async () => { mockFetchResponse({ ok: false, status: 404, statusText: 'Not found', json: jest.fn().mockResolvedValue({ unknownField: 'unknown-error', }), }); await expect(apiClient.remotes.upsert(testOrg, testProject, remotePayload)).rejects.toThrow(new api_client_1.ReuniteApiError('Failed to upsert remote. Not found.', 404)); }); }); describe('push()', () => { const testRemoteId = 'test-remote-id'; const pushPayload = { remoteId: testRemoteId, commit: { message: 'test-message', author: { name: 'test-name', email: 'test-email', }, branchName: 'test-branch-name', }, }; const filesMock = [{ path: 'some-file.yaml', stream: Buffer.from('fefef') }]; const responseMock = { branchName: 'rem/cicd/rem_01he7sr6ys2agb7w0g9t7978fn-main', hasChanges: true, files: [ { type: 'file', name: 'some-file.yaml', path: 'docs/remotes/some-file.yaml', lastModified: 1698925132394.2993, mimeType: 'text/yaml', }, ], commitSha: 'bb23a2f8e012ac0b7b9961b57fb40d8686b21b43', outdated: false, }; let apiClient; beforeEach(() => { apiClient = new api_client_1.ReuniteApi({ domain: testDomain, apiKey: testToken, version, command }); }); it('should push to remote', async () => { let passedFormData = new FormData(); fetch.mockImplementationOnce(async (_, options) => { passedFormData = options.body; return { ok: true, json: jest.fn().mockResolvedValue(responseMock), }; }); const formData = new globalThis.FormData(); formData.append('remoteId', testRemoteId); formData.append('commit[message]', pushPayload.commit.message); formData.append('commit[author][name]', pushPayload.commit.author.name); formData.append('commit[author][email]', pushPayload.commit.author.email); formData.append('commit[branchName]', pushPayload.commit.branchName); formData.append('files[some-file.yaml]', new Blob([filesMock[0].stream])); const result = await apiClient.remotes.push(testOrg, testProject, pushPayload, filesMock); expect(fetch).toHaveBeenCalledWith(`${testDomain}/api/orgs/${testOrg}/projects/${testProject}/pushes`, expect.objectContaining({ method: 'POST', headers: { Authorization: `Bearer ${testToken}`, 'user-agent': expectedUserAgent, }, })); expect(passedFormData).toEqual(formData); expect(result).toEqual(responseMock); }); it('should throw parsed error if response is not ok', async () => { mockFetchResponse({ ok: false, json: jest.fn().mockResolvedValue({ type: 'about:blank', title: 'Cannot push to remote', status: 403, detail: 'Forbidden', object: 'problem', }), }); await expect(apiClient.remotes.push(testOrg, testProject, pushPayload, filesMock)).rejects.toThrow(new api_client_1.ReuniteApiError('Failed to push. Cannot push to remote.', 403)); }); it('should throw statusText error if response is not ok', async () => { mockFetchResponse({ ok: false, status: 404, statusText: 'Not found', json: jest.fn().mockResolvedValue({ unknownField: 'unknown-error', }), }); await expect(apiClient.remotes.push(testOrg, testProject, pushPayload, filesMock)).rejects.toThrow(new api_client_1.ReuniteApiError('Failed to push. Not found.', 404)); }); }); describe('Sunset header', () => { const upsertRemoteMock = { requestFn: () => apiClient.remotes.upsert(testOrg, testProject, { mountBranchName: 'remote-mount-branch-name', mountPath: 'remote-mount-path', }), responseBody: { id: 'remote-id', type: 'CICD', mountPath: 'remote-mount-path', mountBranchName: 'remote-mount-branch-name', organizationId: testOrg, projectId: testProject, }, }; const getDefaultBranchMock = { requestFn: () => apiClient.remotes.getDefaultBranch(testOrg, testProject), responseBody: { branchName: 'test-branch', }, }; const pushMock = { requestFn: () => apiClient.remotes.push(testOrg, testProject, { remoteId: 'test-remote-id', commit: { message: 'test-message', author: { name: 'test-name', email: 'test-email', }, branchName: 'test-branch-name', }, }, [{ path: 'some-file.yaml', stream: Buffer.from('text content') }]), responseBody: { branchName: 'rem/cicd/rem_01he7sr6ys2agb7w0g9t7978fn-main', hasChanges: true, files: [ { type: 'file', name: 'some-file.yaml', path: 'docs/remotes/some-file.yaml', lastModified: 1698925132394.2993, mimeType: 'text/yaml', }, ], commitSha: 'bb23a2f8e012ac0b7b9961b57fb40d8686b21b43', outdated: false, }, }; const endpointMocks = [upsertRemoteMock, getDefaultBranchMock, pushMock]; let apiClient; beforeEach(() => { apiClient = new api_client_1.ReuniteApi({ domain: testDomain, apiKey: testToken, version, command }); }); it.each(endpointMocks)('should report endpoint sunset in the past', async ({ responseBody, requestFn }) => { jest.spyOn(process.stdout, 'write').mockImplementationOnce(() => true); const sunsetDate = new Date('2024-09-06T12:30:32.456Z'); mockFetchResponse({ ok: true, json: jest.fn().mockResolvedValue(responseBody), headers: new Headers({ Sunset: sunsetDate.toISOString(), }), }); await requestFn(); apiClient.reportSunsetWarnings(); expect(process.stdout.write).toHaveBeenCalledWith((0, colorette_1.red)(`The "push" command is not compatible with your version of Redocly CLI. Update to the latest version by running "npm install @redocly/cli@latest".\n\n`)); }); it.each(endpointMocks)('should report endpoint sunset in the future', async ({ responseBody, requestFn }) => { jest.spyOn(process.stdout, 'write').mockImplementationOnce(() => true); const sunsetDate = new Date(Date.now() + 1000 * 60 * 60 * 24); mockFetchResponse({ ok: true, json: jest.fn().mockResolvedValue(responseBody), headers: new Headers({ Sunset: sunsetDate.toISOString(), }), }); await requestFn(); apiClient.reportSunsetWarnings(); expect(process.stdout.write).toHaveBeenCalledWith((0, colorette_1.yellow)(`The "push" command will be incompatible with your version of Redocly CLI after ${sunsetDate.toLocaleString()}. Update to the latest version by running "npm install @redocly/cli@latest".\n\n`)); }); it('should report only expired resource', async () => { jest.spyOn(process.stdout, 'write').mockImplementationOnce(() => true); mockFetchResponse({ ok: true, json: jest.fn().mockResolvedValue(upsertRemoteMock.responseBody), headers: new Headers({ Sunset: new Date('2024-08-06T12:30:32.456Z').toISOString(), }), }); await upsertRemoteMock.requestFn(); mockFetchResponse({ ok: true, json: jest.fn().mockResolvedValue(getDefaultBranchMock.responseBody), headers: new Headers({ Sunset: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(), }), }); await getDefaultBranchMock.requestFn(); mockFetchResponse({ ok: true, json: jest.fn().mockResolvedValue(pushMock.responseBody), headers: new Headers({ Sunset: new Date('2024-08-06T12:30:32.456Z').toISOString(), }), }); await pushMock.requestFn(); apiClient.reportSunsetWarnings(); expect(process.stdout.write).toHaveBeenCalledTimes(1); expect(process.stdout.write).toHaveBeenCalledWith((0, colorette_1.red)(`The "push" command is not compatible with your version of Redocly CLI. Update to the latest version by running "npm install @redocly/cli@latest".\n\n`)); }); }); });