/** * @license * Copyright 2020 Balena Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { BalenaReleaseNotFound } from 'balena-errors'; import { expect } from 'chai'; import * as sinon from 'sinon'; import { ExpectedError } from '../../build/errors'; import { disambiguateReleaseParam } from '../../build/utils/normalization'; describe('disambiguateReleaseParam() function', () => { it('should reject empty values', async () => { try { await disambiguateReleaseParam(null as any, ''); throw new Error('should not be reached'); } catch (e) { expect(e).to.be.an.instanceOf(ExpectedError); expect(e.message).to.equal('Invalid release parameter'); } }); it('should reject values containing invalid chars', async () => { const invalidCharExamples = ' .,-_=!@#$%^&*() '; for (const char of invalidCharExamples) { try { await disambiguateReleaseParam(null as any, char); throw new Error('should not be reached'); } catch (e) { expect(e).to.be.an.instanceOf(ExpectedError); expect(e.message).to.equal('Invalid release parameter'); } } }); it('should reject non-numerical values with invalid uuid/hash lengths', async () => { const invalidLengthValue = 'abcd'; try { await disambiguateReleaseParam(null as any, invalidLengthValue); throw new Error('should not be reached'); } catch (e) { expect(e).to.be.an.instanceOf(ExpectedError); expect(e.message).to.equal('Invalid release parameter'); } }); it('should reject leading-zero numerical values with invalid uuid/hash lengths', async () => { const invalidLengthValue = '01234'; try { await disambiguateReleaseParam(null as any, invalidLengthValue); throw new Error('should not be reached'); } catch (e) { expect(e).to.be.an.instanceOf(ExpectedError); expect(e.message).to.equal('Invalid release parameter'); } }); it('should return non-numerical values with valid hash lengths as string, without SDK calls', async () => { const uuid7 = 'a'.repeat(7); const uuid32 = 'a'.repeat(32); const uuid62 = 'a'.repeat(62); const hash8 = 'a'.repeat(8); const hash9 = 'a'.repeat(9); const hash40 = 'a'.repeat(40); expect(await disambiguateReleaseParam(null as any, uuid7)).to.equal(uuid7); expect(await disambiguateReleaseParam(null as any, uuid32)).to.equal( uuid32, ); expect(await disambiguateReleaseParam(null as any, uuid62)).to.equal( uuid62, ); expect(await disambiguateReleaseParam(null as any, hash8)).to.equal(hash8); expect(await disambiguateReleaseParam(null as any, hash9)).to.equal(hash9); expect(await disambiguateReleaseParam(null as any, hash40)).to.equal( hash40, ); }); it('should return numerical, leading zero values with valid uuid/hash lengths as string, without SDK calls', async () => { const uuid7 = '0' + '1'.repeat(6); const uuid32 = '0' + '1'.repeat(31); const uuid62 = '0' + '1'.repeat(61); const hash8 = '0' + '1'.repeat(7); const hash9 = '0' + '1'.repeat(8); const hash40 = '0' + '1'.repeat(39); expect(await disambiguateReleaseParam(null as any, uuid7)).to.equal(uuid7); expect(await disambiguateReleaseParam(null as any, uuid32)).to.equal( uuid32, ); expect(await disambiguateReleaseParam(null as any, uuid62)).to.equal( uuid62, ); expect(await disambiguateReleaseParam(null as any, hash8)).to.equal(hash8); expect(await disambiguateReleaseParam(null as any, hash9)).to.equal(hash9); expect(await disambiguateReleaseParam(null as any, hash40)).to.equal( hash40, ); }); it('should return id from SDK on first call, if match is found', async () => { const input = '1234'; const output = 1234; const getRelease = sinon.stub().resolves({ id: output }); const sdk: any = { models: { release: { get: getRelease, }, }, }; const result = await disambiguateReleaseParam(sdk, input); expect(result).to.equal(output); expect(getRelease.calledOnce).to.be.true; expect(getRelease.getCall(0).args[0]).to.equal(parseInt(input, 10)); }); it('should return id from SDK on second call, if match is found', async () => { const input = '1234'; const output = 1234; const getRelease = sinon .stub() .onCall(0) .rejects(new BalenaReleaseNotFound(input)) .onCall(1) .resolves({ id: output }); const sdk: any = { models: { release: { get: getRelease, }, }, }; const result = await disambiguateReleaseParam(sdk, input); expect(result).to.equal(output); expect(getRelease.calledTwice).to.be.true; expect(getRelease.getCall(0).args[0]).to.equal(parseInt(input, 10)); expect(getRelease.getCall(1).args[0]).to.equal(input); }); it('should throw error if no match found', async () => { const input = '1234'; const getRelease = sinon.stub().rejects(new BalenaReleaseNotFound(input)); const sdk: any = { models: { release: { get: getRelease, }, }, }; try { await disambiguateReleaseParam(sdk, input); throw new Error('should not be reached'); } catch (e) { expect(e).to.be.an.instanceOf(BalenaReleaseNotFound); expect(getRelease.calledTwice).to.be.true; } }); it('should throw error if unknown error returned from SDK', async () => { const input = '1234'; const getRelease = sinon.stub().rejects(new Error('some error')); const sdk: any = { models: { release: { get: getRelease, }, }, }; try { await disambiguateReleaseParam(sdk, input); throw new Error('should not be reached'); } catch (e) { expect(e).to.be.an.instanceOf(Error); expect(e.message).to.equal('some error'); expect(getRelease.calledOnce).to.be.true; } }); });