Skip to content

Commit

Permalink
feat: report endpoint sunset (#1677)
Browse files Browse the repository at this point in the history
  • Loading branch information
roman-sainchuk authored Sep 24, 2024
1 parent a324686 commit 1fc5471
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 57 deletions.
5 changes: 5 additions & 0 deletions .changeset/tiny-boats-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@redocly/cli": patch
---

Added a warning message to the `push` and `push-status` commands to notify users about upcoming or ongoing resource deprecation.
195 changes: 179 additions & 16 deletions packages/cli/src/cms/api/__tests__/api.client.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import fetch, { Response } from 'node-fetch';
import * as FormData from 'form-data';
import { red, yellow } from 'colorette';

import { ReuniteApiClient, PushPayload, ReuniteApiError } from '../api-client';
import { ReuniteApi, PushPayload, ReuniteApiError } from '../api-client';

jest.mock('node-fetch', () => ({
default: jest.fn(),
Expand All @@ -21,10 +22,10 @@ describe('ApiClient', () => {
const expectedUserAgent = `redocly-cli/${version} ${command}`;

describe('getDefaultBranch()', () => {
let apiClient: ReuniteApiClient;
let apiClient: ReuniteApi;

beforeEach(() => {
apiClient = new ReuniteApiClient({ domain: testDomain, apiKey: testToken, version, command });
apiClient = new ReuniteApi({ domain: testDomain, apiKey: testToken, version, command });
});

it('should get default project branch', async () => {
Expand Down Expand Up @@ -90,22 +91,23 @@ describe('ApiClient', () => {
mountBranchName: 'remote-mount-branch-name',
mountPath: 'remote-mount-path',
};
let apiClient: ReuniteApiClient;

const responseMock = {
id: 'remote-id',
type: 'CICD',
mountPath: 'remote-mount-path',
mountBranchName: 'remote-mount-branch-name',
organizationId: testOrg,
projectId: testProject,
};

let apiClient: ReuniteApi;

beforeEach(() => {
apiClient = new ReuniteApiClient({ domain: testDomain, apiKey: testToken, version, command });
apiClient = new ReuniteApi({ domain: testDomain, apiKey: testToken, version, command });
});

it('should upsert remote', async () => {
const responseMock = {
id: 'remote-id',
type: 'CICD',
mountPath: 'remote-mount-path',
mountBranchName: 'remote-mount-branch-name',
organizationId: testOrg,
projectId: testProject,
};

mockFetchResponse({
ok: true,
json: jest.fn().mockResolvedValue(responseMock),
Expand Down Expand Up @@ -204,10 +206,10 @@ describe('ApiClient', () => {
outdated: false,
};

let apiClient: ReuniteApiClient;
let apiClient: ReuniteApi;

beforeEach(() => {
apiClient = new ReuniteApiClient({ domain: testDomain, apiKey: testToken, version, command });
apiClient = new ReuniteApi({ domain: testDomain, apiKey: testToken, version, command });
});

it('should push to remote', async () => {
Expand Down Expand Up @@ -284,4 +286,165 @@ describe('ApiClient', () => {
).rejects.toThrow(new 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: ReuniteApi;

beforeEach(() => {
apiClient = new 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(
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(
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(
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`
)
);
});
});
});
Loading

1 comment on commit 1fc5471

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report

St.
Category Percentage Covered / Total
🟡 Statements 78.62% 4986/6342
🟡 Branches 67.3% 2058/3058
🟡 Functions 72.94% 822/1127
🟡 Lines 78.91% 4704/5961

Test suite run success

809 tests passing in 121 suites.

Report generated by 🧪jest coverage report action from 1fc5471

Please sign in to comment.