unleash-server
Version:
Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.
200 lines • 8.59 kB
JavaScript
import { createTestConfig } from '../../test/config/test-config.js';
import { ApiTokenType } from '../types/model.js';
import { ADMIN_TOKEN_USER, SYSTEM_USER, TEST_AUDIT_USER, } from '../types/index.js';
import { addDays, minutesToMilliseconds, subDays } from 'date-fns';
import { DEFAULT_ENV, extractAuditInfoFromUser } from '../util/index.js';
import { createFakeApiTokenService } from '../features/api-tokens/createApiTokenService.js';
import { API_TOKEN_CREATED, API_TOKEN_DELETED, API_TOKEN_UPDATED, } from '../events/index.js';
import { vi } from 'vitest';
test('Should init api token', async () => {
const token = {
environment: '*',
projects: ['*'],
secret: '*:*:some-random-string',
type: ApiTokenType.ADMIN,
tokenName: 'admin',
};
const config = createTestConfig({
authentication: {
initApiTokens: [token],
},
experimental: {
flags: {
useMemoizedActiveTokens: true,
},
},
});
const { apiTokenService, apiTokenStore } = createFakeApiTokenService(config);
const insertCalled = new Promise((resolve) => {
apiTokenStore.on('insert', resolve);
});
apiTokenService.initApiTokens([token]);
await insertCalled;
const tokens = await apiTokenStore.getAll();
expect(tokens).toHaveLength(1);
});
test("Shouldn't return frontend token when secret is undefined", async () => {
const token = {
environment: DEFAULT_ENV,
projects: ['*'],
secret: '*:*:some-random-string',
type: ApiTokenType.FRONTEND,
tokenName: 'front',
expiresAt: undefined,
};
const config = createTestConfig({});
const { environmentStore, apiTokenService } = createFakeApiTokenService(config);
await environmentStore.create({
name: DEFAULT_ENV,
enabled: true,
type: 'test',
sortOrder: 1,
});
await apiTokenService.createApiTokenWithProjects(token);
await apiTokenService.fetchActiveTokens();
expect(await apiTokenService.getUserForToken('')).toEqual(undefined);
});
test('Api token operations should all have events attached', async () => {
const token = {
environment: DEFAULT_ENV,
projects: ['*'],
secret: '*:*:some-random-string',
type: ApiTokenType.FRONTEND,
tokenName: 'front',
expiresAt: undefined,
};
const config = createTestConfig({});
const { environmentStore, apiTokenService, eventService } = createFakeApiTokenService(config);
await environmentStore.create({
name: DEFAULT_ENV,
enabled: true,
type: 'test',
sortOrder: 1,
});
const saved = await apiTokenService.createApiTokenWithProjects(token);
const newExpiry = addDays(new Date(), 30);
await apiTokenService.updateExpiry(saved.secret, newExpiry, TEST_AUDIT_USER);
await apiTokenService.delete(saved.secret, TEST_AUDIT_USER);
const { events } = await eventService.getEvents();
const createdApiTokenEvents = events.filter((e) => e.type === API_TOKEN_CREATED);
expect(createdApiTokenEvents).toHaveLength(1);
expect(createdApiTokenEvents[0].preData).toBeUndefined();
expect(createdApiTokenEvents[0].data.secret).toBeUndefined();
const updatedApiTokenEvents = events.filter((e) => e.type === API_TOKEN_UPDATED);
expect(updatedApiTokenEvents).toHaveLength(1);
expect(updatedApiTokenEvents[0].preData.expiresAt).toBeUndefined();
expect(updatedApiTokenEvents[0].preData.secret).toBeUndefined();
expect(updatedApiTokenEvents[0].data.secret).toBeUndefined();
expect(updatedApiTokenEvents[0].data.expiresAt).toBe(newExpiry);
const deletedApiTokenEvents = events.filter((e) => e.type === API_TOKEN_DELETED);
expect(deletedApiTokenEvents).toHaveLength(1);
expect(deletedApiTokenEvents[0].data).toBeUndefined();
expect(deletedApiTokenEvents[0].preData).toBeDefined();
expect(deletedApiTokenEvents[0].preData.secret).toBeUndefined();
});
test('getUserForToken should get a user with admin token user id and token name', async () => {
const config = createTestConfig();
const { apiTokenService } = createFakeApiTokenService(config);
const token = await apiTokenService.createApiTokenWithProjects({
environment: '*',
projects: ['*'],
type: ApiTokenType.ADMIN,
tokenName: 'admin.token',
}, extractAuditInfoFromUser(ADMIN_TOKEN_USER));
const user = await apiTokenService.getUserForToken(token.secret);
expect(user).toBeDefined();
expect(user.username).toBe(token.tokenName);
expect(user.internalAdminTokenUserId).toBe(ADMIN_TOKEN_USER.id);
});
describe('API token getTokenWithCache', () => {
const token = {
environment: DEFAULT_ENV,
projects: ['*'],
secret: '*:*:some-random-string',
type: ApiTokenType.CLIENT,
tokenName: 'new-token-by-another-instance',
expiresAt: undefined,
};
const setup = (options) => {
const config = createTestConfig(options);
const { apiTokenService, apiTokenStore } = createFakeApiTokenService(config);
return {
apiTokenService,
apiTokenStore,
};
};
test('should return the token and perform only one db query', async () => {
const { apiTokenService, apiTokenStore } = setup();
const apiTokenStoreGet = vi.spyOn(apiTokenStore, 'get');
// valid token not present in cache (could be inserted by another instance)
apiTokenStore.insert(token);
for (let i = 0; i < 5; i++) {
const found = await apiTokenService.getTokenWithCache(token.secret);
expect(found).toBeDefined();
expect(found?.tokenName).toBe(token.tokenName);
expect(found?.createdAt).toBeDefined();
}
expect(apiTokenStoreGet).toHaveBeenCalledTimes(1);
});
test('should query the db only once for invalid tokens', async () => {
vi.useFakeTimers();
const { apiTokenService, apiTokenStore } = setup();
const apiTokenStoreGet = vi.spyOn(apiTokenStore, 'get');
const invalidToken = 'invalid-token';
for (let i = 0; i < 5; i++) {
expect(await apiTokenService.getTokenWithCache(invalidToken)).toBeUndefined();
}
expect(apiTokenStoreGet).toHaveBeenCalledTimes(1);
// after more than 5 minutes we should be able to query again
vi.advanceTimersByTime(minutesToMilliseconds(6));
for (let i = 0; i < 5; i++) {
expect(await apiTokenService.getTokenWithCache(invalidToken)).toBeUndefined();
}
expect(apiTokenStoreGet).toHaveBeenCalledTimes(2);
});
test('should not return the token if it has expired and shoud perform only one db query', async () => {
const { apiTokenService, apiTokenStore } = setup();
const apiTokenStoreGet = vi.spyOn(apiTokenStore, 'get');
// valid token not present in cache but expired
apiTokenStore.insert({ ...token, expiresAt: subDays(new Date(), 1) });
for (let i = 0; i < 5; i++) {
const found = await apiTokenService.getTokenWithCache(token.secret);
expect(found).toBeUndefined();
}
expect(apiTokenStoreGet).toHaveBeenCalledTimes(1);
});
});
test('normalizes api token type casing to lowercase', async () => {
const config = createTestConfig();
const { apiTokenStore, apiTokenService, environmentStore } = createFakeApiTokenService(config);
await environmentStore.create({
name: DEFAULT_ENV,
enabled: true,
type: 'test',
sortOrder: 1,
});
const apiTokenStoreInsert = vi.spyOn(apiTokenStore, 'insert');
await apiTokenService.createApiTokenWithProjects({
environment: DEFAULT_ENV,
// @ts-ignore
type: 'CLIENT',
projects: [],
tokenName: 'uppercase-token',
}, SYSTEM_USER);
await apiTokenService.createApiTokenWithProjects({
environment: DEFAULT_ENV,
// @ts-ignore
type: 'client',
projects: [],
tokenName: 'lowercase-token',
}, SYSTEM_USER);
expect(apiTokenStoreInsert).toHaveBeenCalledWith(expect.objectContaining({
type: 'client',
}));
expect(apiTokenStoreInsert).not.toHaveBeenCalledWith(expect.objectContaining({
type: 'CLIENT',
}));
const tokens = await apiTokenStore.getAll();
expect(tokens.every((token) => token.type === 'client')).toBeTruthy();
});
//# sourceMappingURL=api-token-service.test.js.map