Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ fileignoreconfig:
- filename: packages/contentstack-audit/test/unit/mock/am-contents/environments/environments.json
checksum: ffaaee9269a6e833cd3dbe337ddc5c060cce948519873c7a2777754519a31b52
- filename: pnpm-lock.yaml
checksum: 649aae748e3ae8c157439dc6c7c9ea402cc19b4dab7ca64b7e4d839d1375df53
checksum: fda8964d238e0d30577ce8f5bf879a2102c69a7f9e83067ab1627b9b1db9d066
- filename: packages/contentstack-audit/src/types/content-types.ts
checksum: d16a65415c3184f15a807d58e5858310aeb5633794fc9075e36f89c94da636c3
- filename: packages/contentstack-audit/src/audit-base-command.ts
Expand Down Expand Up @@ -39,4 +39,6 @@ fileignoreconfig:
checksum: a64a4d396eddd936a63b799eff58c5c6660b5dcaa3a310fd8b09a027932f1789
- filename: packages/contentstack-migration/README.md
checksum: e96006c1a948f766c88ae972b29582fa58eaf8184606bf011eebddc5a06cd7b6
- filename: packages/contentstack-bootstrap/test/github.test.js
checksum: b7badfcd3bbad0cb876364542bba26cdfd854f1b138be2896b5f84c219767040
version: ""
1 change: 1 addition & 0 deletions packages/contentstack-bootstrap/messages/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"CLI_BOOTSTRAP_GITHUB_ACCESS_NOT_FOUND": "No Github access token found",
"CLI_BOOTSTRAP_START_CLONE_APP": "Cloning the selected app",
"CLI_BOOTSTRAP_REPO_NOT_FOUND": "Unable to find a repo for \"%s\"",
"CLI_BOOTSTRAP_APP_UNAVAILABLE": "Unable to download \"%s\": branch \"cli-use\" not found. Ensure the branch exists on the GitHub repository.",
"CLI_BOOTSTRAP_NO_API_KEY_FOUND": "No API key generated for the stack",
"CLI_BOOTSTRAP_STACK_CREATION_FAILED": "Unable to create stack for content \"%s\"",
"CLI_BOOTSTRAP_APP_SELECTION_ENQUIRY": "Select an App",
Expand Down
12 changes: 11 additions & 1 deletion packages/contentstack-bootstrap/src/bootstrap/github/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,23 @@ export default class GitHubClient {
}

const response = await HttpClient.create().options(options).get(url);

if (response.status >= 400) {
throw new GithubError(
messageHandler.parse('CLI_BOOTSTRAP_REPO_NOT_FOUND', `${this.repo.user}/${this.repo.name}`),
response.status,
);
}

return response.data as Stream;
}

async extract(destination: string, stream: Stream): Promise<any> {
return new Promise((resolve, reject) => {
const unzip = zlib.createUnzip();
unzip.on('error', reject);
stream
.pipe(zlib.createUnzip())
.pipe(unzip)
.pipe(
tar.extract({
cwd: destination,
Expand Down
10 changes: 4 additions & 6 deletions packages/contentstack-bootstrap/src/bootstrap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,13 @@ export default class Bootstrap {

try {
await this.ghClient.getLatest(this.cloneDirectory);
cliux.loader();
} catch (error) {
if (error instanceof GithubError) {
if (error.status === 404) {
cliux.error(messageHandler.parse('CLI_BOOTSTRAP_REPO_NOT_FOUND', this.appConfig.source));
}
cliux.loader();
if (error instanceof GithubError && error.status === 404) {
throw new Error(messageHandler.parse('CLI_BOOTSTRAP_APP_UNAVAILABLE', this.appConfig.source));
}
throw error;
} finally {
cliux.loader();
}

// seed plugin start
Expand Down
106 changes: 106 additions & 0 deletions packages/contentstack-bootstrap/test/github.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const { expect } = require('chai');
const sinon = require('sinon');
const { Readable } = require('stream');
const { HttpClient } = require('@contentstack/cli-utilities');
const GitHubClient = require('../lib/bootstrap/github/client').default;
const GithubError = require('../lib/bootstrap/github/github-error').default;

describe('Github Client', function () {
it('Parse github url', () => {
Expand All @@ -16,4 +20,106 @@ describe('Github Client', function () {
'https://api.github.com/repos/contentstack/contentstack-nextjs-react-universal-demo/tarball/cli-use',
);
});

describe('streamRelease', function () {
let sandbox;

beforeEach(() => {
sandbox = sinon.createSandbox();
});

afterEach(() => {
sandbox.restore();
});

it('should throw GithubError with status 404 when the branch does not exist', async () => {
const notFoundStream = new Readable({ read() {} });
notFoundStream.push(Buffer.from('404: Not Found'));
notFoundStream.push(null);

const httpStub = { get: sandbox.stub().resolves({ status: 404, data: notFoundStream }), options: sandbox.stub().returnsThis() };
sandbox.stub(HttpClient, 'create').returns(httpStub);

const client = new GitHubClient(GitHubClient.parsePath('contentstack/kickstart-next'));

try {
await client.streamRelease(client.gitTarBallUrl);
throw new Error('Expected GithubError to be thrown');
} catch (err) {
expect(err).to.be.instanceOf(GithubError);
expect(err.status).to.equal(404);
}
});

it('should throw GithubError with status 500 on server error', async () => {
const errStream = new Readable({ read() {} });
errStream.push(Buffer.from('Internal Server Error'));
errStream.push(null);

const httpStub = { get: sandbox.stub().resolves({ status: 500, data: errStream }), options: sandbox.stub().returnsThis() };
sandbox.stub(HttpClient, 'create').returns(httpStub);

const client = new GitHubClient(GitHubClient.parsePath('contentstack/kickstart-next'));

try {
await client.streamRelease(client.gitTarBallUrl);
throw new Error('Expected GithubError to be thrown');
} catch (err) {
expect(err).to.be.instanceOf(GithubError);
expect(err.status).to.equal(500);
}
});

it('should return the response stream when status is 200', async () => {
const mockStream = new Readable({ read() {} });
const httpStub = { get: sandbox.stub().resolves({ status: 200, data: mockStream }), options: sandbox.stub().returnsThis() };
sandbox.stub(HttpClient, 'create').returns(httpStub);

const client = new GitHubClient(GitHubClient.parsePath('contentstack/kickstart-next'));
const result = await client.streamRelease(client.gitTarBallUrl);

expect(result).to.equal(mockStream);
});

it('should pass Authorization header for private repos', async () => {
const mockStream = new Readable({ read() {} });
const httpStub = { get: sandbox.stub().resolves({ status: 200, data: mockStream }), options: sandbox.stub().returnsThis() };
sandbox.stub(HttpClient, 'create').returns(httpStub);

const client = new GitHubClient(GitHubClient.parsePath('contentstack/private-repo'), true, 'my-token');
await client.streamRelease(client.gitTarBallUrl);

const callOptions = httpStub.options.firstCall.args[0];
expect(callOptions.headers).to.deep.equal({ Authorization: 'token my-token' });
});

it('should throw GithubError immediately for private repos with no access token', async () => {
const client = new GitHubClient(GitHubClient.parsePath('contentstack/private-repo'), true, undefined);

try {
await client.streamRelease(client.gitTarBallUrl);
throw new Error('Expected GithubError to be thrown');
} catch (err) {
expect(err).to.be.instanceOf(GithubError);
expect(err.status).to.equal(1);
}
});
});

describe('extract', function () {
it('should reject (not crash the process) when the stream contains invalid gzip data', async () => {
const client = new GitHubClient(GitHubClient.parsePath('contentstack/kickstart-next'));

const badStream = new Readable({ read() {} });
badStream.push(Buffer.from('404: Not Found'));
badStream.push(null);

try {
await client.extract('/tmp', badStream);
throw new Error('Expected extraction error to be thrown');
} catch (err) {
expect(err.code).to.equal('Z_DATA_ERROR');
}
});
});
});
Loading