302 lines
6.7 KiB
TypeScript
302 lines
6.7 KiB
TypeScript
'use strict';
|
|
|
|
import 'mocha';
|
|
import { expect } from 'chai';
|
|
|
|
import { tmpdir } from 'os';
|
|
import { existsSync, readFileSync } from 'fs';
|
|
|
|
import {
|
|
buildDomainWideDelegationJWT,
|
|
explodeStrings,
|
|
fromBase64,
|
|
parseDuration,
|
|
removeExportedCredentials,
|
|
toBase64,
|
|
trimmedString,
|
|
writeSecureFile,
|
|
} from '../src/utils';
|
|
|
|
describe('Utils', () => {
|
|
describe('#writeSecureFile', () => {
|
|
it('writes data to the file', async () => {
|
|
const tmp = tmpdir();
|
|
const filePath = await writeSecureFile(tmp, 'hi');
|
|
expect(existsSync(filePath)).to.be.true;
|
|
expect(readFileSync(filePath).toString('utf8')).to.eq('hi');
|
|
});
|
|
|
|
it('generates a random name', async () => {
|
|
const tmp = tmpdir();
|
|
const filePath1 = await writeSecureFile(tmp, 'hi');
|
|
const filePath2 = await writeSecureFile(tmp, 'bye');
|
|
expect(filePath1).to.not.eq(filePath2);
|
|
expect(filePath1).to.not.eq(filePath2);
|
|
});
|
|
});
|
|
|
|
describe('#removeExportedCredentials', () => {
|
|
it('does nothing when GOOGLE_GHA_CREDS_PATH is unset', async () => {
|
|
delete process.env.GOOGLE_GHA_CREDS_PATH;
|
|
const pth = await removeExportedCredentials();
|
|
expect(pth).to.eq('');
|
|
});
|
|
|
|
it('deletes the file', async () => {
|
|
const filePath = await writeSecureFile(tmpdir(), 'my data');
|
|
process.env.GOOGLE_GHA_CREDS_PATH = filePath;
|
|
const pth = await removeExportedCredentials();
|
|
expect(existsSync(filePath)).to.be.false;
|
|
expect(pth).to.eq(filePath);
|
|
});
|
|
|
|
it('does not fail if the file does not exist', async () => {
|
|
const filePath = '/not/a/file';
|
|
process.env.GOOGLE_GHA_CREDS_PATH = filePath;
|
|
const pth = await removeExportedCredentials();
|
|
expect(pth).to.eq('');
|
|
});
|
|
});
|
|
|
|
describe('#explodeStrings', () => {
|
|
const cases = [
|
|
{
|
|
name: 'empty string',
|
|
input: '',
|
|
expected: [],
|
|
},
|
|
{
|
|
name: 'padded empty string',
|
|
input: ' ',
|
|
expected: [],
|
|
},
|
|
{
|
|
name: 'comma-separated',
|
|
input: 'hello , world , and goodbye',
|
|
expected: ['hello', 'world', 'and goodbye'],
|
|
},
|
|
{
|
|
name: 'newline-separated',
|
|
input: `
|
|
hello
|
|
world
|
|
and goodbye`,
|
|
expected: ['hello', 'world', 'and goodbye'],
|
|
},
|
|
{
|
|
name: 'comma and newline-separated',
|
|
input: `
|
|
hello,
|
|
world,
|
|
and goodbye,`,
|
|
expected: ['hello', 'world', 'and goodbye'],
|
|
},
|
|
{
|
|
name: 'comma-escaped',
|
|
input: 'hello , world , and\\, goodbye',
|
|
expected: ['hello', 'world', 'and, goodbye'],
|
|
},
|
|
];
|
|
|
|
cases.forEach((tc) => {
|
|
it(tc.name, async () => {
|
|
expect(explodeStrings(tc.input)).to.eql(tc.expected);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#toBase64', () => {
|
|
const cases = [
|
|
{
|
|
name: 'empty string',
|
|
input: '',
|
|
expected: '',
|
|
},
|
|
{
|
|
name: 'empty buffer',
|
|
input: Buffer.from(''),
|
|
expected: '',
|
|
},
|
|
{
|
|
name: 'encodes string',
|
|
input: 'hello',
|
|
expected: 'aGVsbG8',
|
|
},
|
|
{
|
|
name: 'encodes buffer',
|
|
input: Buffer.from('hello'),
|
|
expected: 'aGVsbG8',
|
|
},
|
|
];
|
|
|
|
cases.forEach((tc) => {
|
|
it(tc.name, async () => {
|
|
expect(toBase64(tc.input)).to.eq(tc.expected);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#fromBase64', () => {
|
|
const cases = [
|
|
{
|
|
name: 'decodes',
|
|
input: 'aGVsbG8',
|
|
expected: 'hello',
|
|
},
|
|
{
|
|
name: 'decodes padded',
|
|
input: 'aGVsbG8==',
|
|
expected: 'hello',
|
|
},
|
|
];
|
|
|
|
cases.forEach((tc) => {
|
|
it(tc.name, async () => {
|
|
expect(fromBase64(tc.input)).to.eq(tc.expected);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#trimmedString', () => {
|
|
const cases = [
|
|
{
|
|
name: 'null',
|
|
input: null,
|
|
expected: '',
|
|
},
|
|
{
|
|
name: 'undefined',
|
|
input: undefined,
|
|
expected: '',
|
|
},
|
|
{
|
|
name: 'empty string',
|
|
input: '',
|
|
expected: '',
|
|
},
|
|
{
|
|
name: 'trims',
|
|
input: ' hello world ',
|
|
expected: 'hello world',
|
|
},
|
|
];
|
|
|
|
cases.forEach((tc) => {
|
|
it(tc.name, async () => {
|
|
expect(trimmedString(tc.input)).to.eq(tc.expected);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#parseDuration', () => {
|
|
const cases = [
|
|
{
|
|
name: 'empty string',
|
|
input: '',
|
|
expected: 0,
|
|
},
|
|
{
|
|
name: 'unitless',
|
|
input: '149585',
|
|
expected: 149585,
|
|
},
|
|
{
|
|
name: 'with commas',
|
|
input: '149,585',
|
|
expected: 149585,
|
|
},
|
|
{
|
|
name: 'suffix seconds',
|
|
input: '149585s',
|
|
expected: 149585,
|
|
},
|
|
{
|
|
name: 'suffix minutes',
|
|
input: '25m',
|
|
expected: 1500,
|
|
},
|
|
{
|
|
name: 'suffix hours',
|
|
input: '12h',
|
|
expected: 43200,
|
|
},
|
|
{
|
|
name: 'suffix hours minutes seconds',
|
|
input: '12h10m55s',
|
|
expected: 43855,
|
|
},
|
|
{
|
|
name: 'commas and spaces',
|
|
input: '12h, 10m 55s',
|
|
expected: 43855,
|
|
},
|
|
{
|
|
name: 'invalid',
|
|
input: '12h blueberries',
|
|
error: 'Unsupported character "b" at position 4',
|
|
},
|
|
];
|
|
|
|
cases.forEach((tc) => {
|
|
it(tc.name, async () => {
|
|
if (tc.expected) {
|
|
expect(parseDuration(tc.input)).to.eq(tc.expected);
|
|
} else if (tc.error) {
|
|
expect(() => {
|
|
parseDuration(tc.input);
|
|
}).to.throw(tc.error);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#buildDomainWideDelegationJWT', () => {
|
|
const cases = [
|
|
{
|
|
name: 'default',
|
|
serviceAccount: 'my-service@example.com',
|
|
lifetime: 1000,
|
|
},
|
|
{
|
|
name: 'with subject',
|
|
serviceAccount: 'my-service@example.com',
|
|
subject: 'my-subject',
|
|
lifetime: 1000,
|
|
},
|
|
{
|
|
name: 'with scopes',
|
|
serviceAccount: 'my-service@example.com',
|
|
scopes: ['scope1', 'scope2'],
|
|
lifetime: 1000,
|
|
},
|
|
];
|
|
|
|
cases.forEach((tc) => {
|
|
it(tc.name, async () => {
|
|
const val = buildDomainWideDelegationJWT(
|
|
tc.serviceAccount,
|
|
tc.subject,
|
|
tc.scopes,
|
|
tc.lifetime,
|
|
);
|
|
|
|
const body = JSON.parse(val);
|
|
expect(body.iss).to.eq(tc.serviceAccount);
|
|
expect(body.aud).to.eq('https://oauth2.googleapis.com/token');
|
|
|
|
if (tc.subject) {
|
|
expect(body.sub).to.eq(tc.subject);
|
|
} else {
|
|
expect(body.sub).to.not.be;
|
|
}
|
|
|
|
if (tc.scopes) {
|
|
expect(body.scope).to.eq(tc.scopes.join(' '));
|
|
} else {
|
|
expect(body.scope).to.not.be;
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|