auth/tests/utils.test.ts
2021-12-02 10:17:06 -06:00

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;
}
});
});
});
});