import {AssertionError, expect} from 'chai'; import {suite, test} from 'mocha'; import {mainImpl, validateEmail, validateName} from './get-license.js'; import type {SimpleFetch} from '../client/index.js'; import {PROD_LICENSE_SERVER_URL} from '../client/index.js'; import prompts from 'prompts'; import {Writable} from 'stream'; import * as sinon from 'sinon'; suite('CLI: get-license', () => { test('validateName', () => { expect(validateName('')).to.be.false; expect(validateName(' ')).to.be.false; expect(validateName('foo')).to.be.true; expect(validateName('foo bar')).to.be.true; }); test('validateEmail', () => { expect(validateEmail('')).to.be.false; expect(validateEmail(' ')).to.be.false; expect(validateEmail('@')).to.be.false; expect(validateEmail('foo@')).to.be.false; expect(validateEmail('foo@bar.com')).to.be.true; }); type getLicenseTestCase = { name: string; project: string; email: string; commercial: boolean; acceptedTOS: boolean; expProject: string | undefined; expCreateCustomerCalled: boolean; throwError: undefined | string | Error | number; licenseKey: string; }; async function getLicenseTest(tc: getLicenseTestCase) { const exitStub = sinon.stub(process, 'exit'); // TODO would be better to use the fetch function, otherwise we can't be sure it has // the desired behavior eg throwing on non-200. const fakeCreateCustomer = async ( _: SimpleFetch, serverURL: URL, name: string, project: string | undefined, email: string, commercial: boolean, acceptedTOS: boolean, // eslint-disable-next-line require-await ): Promise => { expect(tc.expCreateCustomerCalled).to.be.true; if (tc.throwError) { throw tc.throwError; } expect(serverURL.toString()).to.equal(PROD_LICENSE_SERVER_URL.toString()); expect(name).to.equal(tc.name); expect(project).to.equal(tc.expProject); expect(email).to.equal(tc.email); expect(commercial).to.equal( tc.commercial, `commercial expected ${tc.commercial}, got ${commercial}`, ); expect(acceptedTOS).to.be.true; return tc.licenseKey; }; prompts.inject([ tc.name, tc.project, tc.email, tc.commercial, tc.acceptedTOS, ]); const stdout = new CaptureStream(); try { await mainImpl(PROD_LICENSE_SERVER_URL, fakeCreateCustomer, stdout); } catch (err) { // If the code throws an AssertionError then one of its expectations // failed, so let that error bubble up. if (err instanceof AssertionError) { throw err; } // We should never get an error here. All errors should be caught at the top level // and printed nicely to stdout. expect(err).to.be.undefined; } if (tc.throwError) { expect(exitStub.calledWith(1)); expect(stdout.captured).to.match(/error/i); if (tc.throwError instanceof Error) { expect(stdout.captured).to.match(new RegExp(tc.throwError.message)); } else if (typeof tc.throwError === 'string') { expect(stdout.captured).to.match(new RegExp(tc.throwError)); } } else { expect( exitStub.calledWith(0), `exit called with ${exitStub.args[0]}, stdout: ${stdout.captured}`, ); expect(stdout.captured).to.not.match(/error/i); expect(stdout.captured).to.match(new RegExp(`${tc.licenseKey}`)); } } beforeEach(() => { sinon.restore(); }); afterEach(() => { sinon.restore(); }); const goodResponse = { name: 'name', project: 'project', email: 'foo@example.com', commercial: true, acceptedTOS: true, }; test('good inputs', async () => { await getLicenseTest({ ...goodResponse, expProject: 'project', expCreateCustomerCalled: true, throwError: undefined, licenseKey: 'good-license-key', }); }); test('good inputs, no project', async () => { await getLicenseTest({ ...goodResponse, project: '', expProject: undefined, expCreateCustomerCalled: true, throwError: undefined, licenseKey: 'good-license-key', }); }); test("declining ToS doesn't create customer", async () => { await getLicenseTest({ ...goodResponse, acceptedTOS: false, expProject: undefined, expCreateCustomerCalled: false, throwError: undefined, licenseKey: '', }); }); test('createCustomer throws an Error', async () => { await getLicenseTest({ ...goodResponse, expProject: 'project', expCreateCustomerCalled: true, throwError: new Error('boom'), licenseKey: 'no-key', }); }); test('createCustomer throws a string', async () => { await getLicenseTest({ ...goodResponse, expCreateCustomerCalled: true, expProject: 'project', throwError: 'boom', licenseKey: 'no-key', }); }); test('createCustomer throws a random thing like a number', async () => { await getLicenseTest({ ...goodResponse, expProject: 'project', expCreateCustomerCalled: true, throwError: 42, licenseKey: 'no-key', }); }); }); class CaptureStream extends Writable { public captured = ''; // eslint-disable-next-line @typescript-eslint/naming-convention _write(chunk: {toString: () => string}, _: unknown, next: () => void) { this.captured += chunk.toString(); next(); } }