Files
gradle/sources/test/jest/short-lived-token.test.ts
9c445f57df Support experimental project-entry caching (configuration-cache + build-logic) (#994)
Pass develocityAccessToken and develocityServerUrl the
`gradle-actions-caching`: required to support project-entry caching
(build-logic + configuration-cache), which has experimental support in
'gradle-actions-cache@v0.8.0. This support is not yet released and will
be available as a restricted trial.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:14:19 +00:00

176 lines
6.4 KiB
TypeScript

import nock from "nock";
import {describe, expect, it} from '@jest/globals'
import {DevelocityAccessCredentials, getToken, resolveTokenForServer} from "../../src/develocity/short-lived-token";
describe('short lived tokens', () => {
it('parse valid access key should return an object', async () => {
let develocityAccessCredentials = DevelocityAccessCredentials.parse('some-host.local=key1;host2=key2');
expect(develocityAccessCredentials).toStrictEqual(DevelocityAccessCredentials.of([
{hostname: 'some-host.local', key: 'key1'},
{hostname: 'host2', key: 'key2'}])
)
})
it('parse wrong access key should return null', async () => {
let develocityAccessCredentials = DevelocityAccessCredentials.parse('random;foo');
expect(develocityAccessCredentials).toBeNull()
})
it('parse empty access key should return null', async () => {
let develocityAccessCredentials = DevelocityAccessCredentials.parse('');
expect(develocityAccessCredentials).toBeNull()
})
it('access key as raw string', async () => {
let develocityAccessCredentials = DevelocityAccessCredentials.parse('host1=key1;host2=key2');
expect(develocityAccessCredentials?.raw()).toBe('host1=key1;host2=key2')
})
it('get short lived token returns null when access key is empty', async () => {
expect.assertions(1)
await expect(getToken('', false, ''))
.resolves
.toBeNull()
})
it('get short lived token succeeds when single key is set', async () => {
nock('https://dev')
.post('/api/auth/token')
.reply(200, 'token')
expect.assertions(1)
await expect(getToken('dev=key1', false, ''))
.resolves
.toEqual({"keys": [{"hostname": "dev", "key": "token"}]})
})
it('get short lived token succeeds when multiple keys are set', async () => {
nock('https://dev')
.post('/api/auth/token')
.reply(200, 'token1')
nock('https://prod')
.post('/api/auth/token')
.reply(200, 'token2')
expect.assertions(1)
await expect(getToken('dev=key1;prod=key2', false, ''))
.resolves
.toEqual({"keys": [{"hostname": "dev", "key": "token1"}, {"hostname": "prod", "key": "token2"}]})
})
it('get short lived token succeeds when multiple keys are set and one is failing', async () => {
nock('https://dev')
.post('/api/auth/token')
.reply(200, 'token1')
nock('https://bogus')
.post('/api/auth/token')
.times(3)
.reply(500, 'Internal Error')
nock('https://prod')
.post('/api/auth/token')
.reply(200, 'token2')
expect.assertions(1)
await expect(getToken('dev=key1;bogus=key0;prod=key2', false, ''))
.resolves
.toEqual({"keys": [{"hostname": "dev", "key": "token1"}, {"hostname": "prod", "key": "token2"}]})
})
it('get short lived token is null when multiple keys are set and all are failing', async () => {
nock('https://dev')
.post('/api/auth/token')
.times(3)
.reply(500, 'Internal Error')
nock('https://bogus')
.post('/api/auth/token')
.times(3)
.reply(500, 'Internal Error')
expect.assertions(1)
await expect(getToken('dev=key1;bogus=key0', false, ''))
.resolves
.toBeNull()
})
it('get short lived token with custom expiry', async () => {
nock('https://dev')
.post('/api/auth/token?expiresInHours=4')
.reply(200, 'token')
expect.assertions(1)
await expect(getToken('dev=key1', false, '4'))
.resolves
.toEqual({"keys": [{"hostname": "dev", "key": "token"}]})
})
})
describe('short lived tokens with retry', () => {
afterEach(() => {
nock.cleanAll()
nock.restore()
})
it('get short lived token fails when cannot connect', async () => {
nock('http://localhost:3333')
.post('/api/auth/token')
.times(3)
.replyWithError({
message: 'connect ECONNREFUSED 127.0.0.1:3333',
code: 'ECONNREFUSED'
})
await expect(getToken('localhost=key0', false, ''))
.resolves
.toBeNull()
})
it('get short lived token is null when request fails', async () => {
nock('http://dev:3333')
.post('/api/auth/token')
.times(3)
.reply(500, 'Internal error')
expect.assertions(1)
await expect(getToken('dev=xyz', false, ''))
.resolves
.toBeNull()
})
})
describe('resolveTokenForServer', () => {
const credentials = (...pairs: [string, string][]): DevelocityAccessCredentials =>
DevelocityAccessCredentials.of(pairs.map(([hostname, key]) => ({hostname, key})))
it('returns the token matching the server host from a full URL', () => {
const tokens = credentials(['ge.example.com', 'key1'], ['other', 'key2'])
expect(resolveTokenForServer(tokens, 'https://ge.example.com')).toBe('key1')
})
it('matches on hostname, ignoring scheme, port and path', () => {
const tokens = credentials(['ge.example.com', 'key1'])
expect(resolveTokenForServer(tokens, 'https://ge.example.com:8443/path')).toBe('key1')
})
it('tolerates a bare hostname with no scheme', () => {
const tokens = credentials(['ge.example.com', 'key1'])
expect(resolveTokenForServer(tokens, 'ge.example.com')).toBe('key1')
})
it('selects the matching token when multiple are present', () => {
const tokens = credentials(['dev', 'key1'], ['ge.example.com', 'key2'])
expect(resolveTokenForServer(tokens, 'https://ge.example.com')).toBe('key2')
})
it('returns undefined when no token matches the server host', () => {
const tokens = credentials(['ge.example.com', 'key1'])
expect(resolveTokenForServer(tokens, 'https://other.example.com')).toBeUndefined()
})
it('returns undefined for an empty server URL', () => {
const tokens = credentials(['ge.example.com', 'key1'])
expect(resolveTokenForServer(tokens, '')).toBeUndefined()
})
it('returns undefined when there are no tokens', () => {
expect(resolveTokenForServer(credentials(), 'https://ge.example.com')).toBeUndefined()
})
})