Skip to content

Commit

Permalink
chore: extract api token service composition root; place it in /featu…
Browse files Browse the repository at this point in the history
…res (#7519)

This is a refactoring task, creating an ApiTokenService composition root
in /features.
  • Loading branch information
thomasheartman authored Jul 3, 2024
1 parent 57c1a6e commit 6d91380
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 120 deletions.
53 changes: 53 additions & 0 deletions src/lib/features/api-tokens/createApiTokenService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { Db, IUnleashConfig } from '../../server-impl';
import EnvironmentStore from '../project-environments/environment-store';
import { ApiTokenService, type EventService } from '../../services';
import FakeEnvironmentStore from '../project-environments/fake-environment-store';
import type { IEnvironmentStore } from '../../types';
import {
createEventsService,
createFakeEventsService,
} from '../events/createEventsService';
import FakeApiTokenStore from '../../../test/fixtures/fake-api-token-store';
import { ApiTokenStore } from '../../db/api-token-store';

export const createApiTokenService = (
db: Db,
config: IUnleashConfig,
): ApiTokenService => {
const { eventBus, getLogger } = config;
const apiTokenStore = new ApiTokenStore(db, eventBus, getLogger);
const environmentStore = new EnvironmentStore(db, eventBus, getLogger);
const eventService = createEventsService(db, config);

return new ApiTokenService(
{ apiTokenStore, environmentStore },
config,
eventService,
);
};

export const createFakeApiTokenService = (
config: IUnleashConfig,
): {
apiTokenService: ApiTokenService;
eventService: EventService;
apiTokenStore: FakeApiTokenStore;
environmentStore: IEnvironmentStore;
} => {
const apiTokenStore = new FakeApiTokenStore();
const environmentStore = new FakeEnvironmentStore();
const eventService = createFakeEventsService(config);

const apiTokenService = new ApiTokenService(
{ apiTokenStore, environmentStore },
config,
eventService,
);

return {
apiTokenService,
apiTokenStore,
eventService,
environmentStore,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ beforeAll(async () => {
baseLogger.error(msg, ...args);
},
};
app = await setupAppWithoutSupertest(db.stores, {
frontendApiOrigins: ['https://example.com'],
getLogger: () => appLogger,
});
app = await setupAppWithoutSupertest(
db.stores,
{
frontendApiOrigins: ['https://example.com'],
getLogger: () => appLogger,
},
db.rawDatabase,
);
});

afterEach(() => {
Expand Down
16 changes: 10 additions & 6 deletions src/lib/features/frontend-api/frontend-api.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1151,14 +1151,18 @@ test('should NOT evaluate disabled strategies when returning toggles', async ()
});

test('should return 204 if metrics are disabled', async () => {
const localApp = await setupAppWithAuth(db.stores, {
frontendApiOrigins: ['https://example.com'],
experimental: {
flags: {
disableMetrics: true,
const localApp = await setupAppWithAuth(
db.stores,
{
frontendApiOrigins: ['https://example.com'],
experimental: {
flags: {
disableMetrics: true,
},
},
},
});
db.rawDatabase,
);

const frontendToken =
await localApp.services.apiTokenService.createApiTokenWithProjects({
Expand Down
10 changes: 8 additions & 2 deletions src/lib/routes/admin-api/api-token.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@ async function getSetup(adminTokenKillSwitchEnabled: boolean) {
//@ts-ignore - Just testing, so only need the isEnabled call here
});
const stores = createStores();
await stores.environmentStore.create({
const services = createServices(stores, config);

//@ts-expect-error: we're accessing a private field, but we need
//to set up an environment to test the functionality. Because we
//don't have a db to use, we need to access the service's store
//directly.
await services.apiTokenService.environmentStore.create({
name: 'development',
type: 'development',
enabled: true,
});
const services = createServices(stores, config);

const app = await getApp(config, stores, services);

return {
Expand Down
25 changes: 6 additions & 19 deletions src/lib/services/api-token-service.limit.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { ApiTokenService } from './api-token-service';
import { createTestConfig } from '../../test/config/test-config';
import type { IUnleashConfig } from '../server-impl';
import { ApiTokenType } from '../types/models/api-token';
import FakeApiTokenStore from '../../test/fixtures/fake-api-token-store';
import FakeEnvironmentStore from '../features/project-environments/fake-environment-store';
import { createFakeEventsService } from '../../lib/features';
import { ExceedsLimitError } from '../error/exceeds-limit-error';
import { createFakeApiTokenService } from '../features/api-tokens/createApiTokenService';

const createServiceWithLimit = (limit: number) => {
const config: IUnleashConfig = createTestConfig({
Expand All @@ -15,28 +12,18 @@ const createServiceWithLimit = (limit: number) => {
},
},
});
config.resourceLimits.apiTokens = limit;

const { apiTokenService, environmentStore } =
createFakeApiTokenService(config);

const apiTokenStore = new FakeApiTokenStore();
const environmentStore = new FakeEnvironmentStore();
environmentStore.create({
name: 'production',
type: 'production',
enabled: true,
protected: true,
sortOrder: 1,
});

const eventService = createFakeEventsService(config);

config.resourceLimits.apiTokens = limit;

const service = new ApiTokenService(
{ apiTokenStore, environmentStore },
config,
eventService,
);

return service;
return apiTokenService;
};

test('Should allow you to create tokens up to and including the limit', async () => {
Expand Down
82 changes: 11 additions & 71 deletions src/lib/services/api-token-service.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { ApiTokenService } from './api-token-service';
import { createTestConfig } from '../../test/config/test-config';
import type { IUnleashConfig, IUnleashOptions, IUser } from '../server-impl';
import { ApiTokenType, type IApiTokenCreate } from '../types/models/api-token';
import FakeApiTokenStore from '../../test/fixtures/fake-api-token-store';
import FakeEnvironmentStore from '../features/project-environments/fake-environment-store';
import FakeEventStore from '../../test/fixtures/fake-event-store';
import {
ADMIN_TOKEN_USER,
API_TOKEN_CREATED,
Expand All @@ -13,10 +9,8 @@ import {
TEST_AUDIT_USER,
} from '../types';
import { addDays, minutesToMilliseconds, subDays } from 'date-fns';
import EventService from '../features/events/event-service';
import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store';
import { createFakeEventsService } from '../../lib/features';
import { extractAuditInfoFromUser } from '../util';
import { createFakeApiTokenService } from '../features/api-tokens/createApiTokenService';

test('Should init api token', async () => {
const token = {
Expand All @@ -37,20 +31,11 @@ test('Should init api token', async () => {
},
},
});
const apiTokenStore = new FakeApiTokenStore();
const environmentStore = new FakeEnvironmentStore();
const { apiTokenStore } = createFakeApiTokenService(config);
const insertCalled = new Promise((resolve) => {
apiTokenStore.on('insert', resolve);
});

const eventService = createFakeEventsService(config);

new ApiTokenService(
{ apiTokenStore, environmentStore },
config,
eventService,
);

await insertCalled;

const tokens = await apiTokenStore.getAll();
Expand All @@ -69,31 +54,15 @@ test("Shouldn't return frontend token when secret is undefined", async () => {
};

const config: IUnleashConfig = createTestConfig({});
const apiTokenStore = new FakeApiTokenStore();
const environmentStore = new FakeEnvironmentStore();

const eventService = new EventService(
{
eventStore: new FakeEventStore(),
featureTagStore: new FakeFeatureTagStore(),
},
config,
);

const { environmentStore, apiTokenService } =
createFakeApiTokenService(config);
await environmentStore.create({
name: 'default',
enabled: true,
protected: true,
type: 'test',
sortOrder: 1,
});

const apiTokenService = new ApiTokenService(
{ apiTokenStore, environmentStore },
config,
eventService,
);

await apiTokenService.createApiTokenWithProjects(token);
await apiTokenService.fetchActiveTokens();

Expand All @@ -111,30 +80,16 @@ test('Api token operations should all have events attached', async () => {
};

const config: IUnleashConfig = createTestConfig({});
const apiTokenStore = new FakeApiTokenStore();
const environmentStore = new FakeEnvironmentStore();

const eventService = new EventService(
{
eventStore: new FakeEventStore(),
featureTagStore: new FakeFeatureTagStore(),
},
config,
);

const { environmentStore, apiTokenService, eventService } =
createFakeApiTokenService(config);
await environmentStore.create({
name: 'default',
enabled: true,
protected: true,
type: 'test',
sortOrder: 1,
});

const apiTokenService = new ApiTokenService(
{ apiTokenStore, environmentStore },
config,
eventService,
);
const saved = await apiTokenService.createApiTokenWithProjects(token);
const newExpiry = addDays(new Date(), 30);
await apiTokenService.updateExpiry(
Expand Down Expand Up @@ -171,17 +126,8 @@ test('Api token operations should all have events attached', async () => {

test('getUserForToken should get a user with admin token user id and token name', async () => {
const config = createTestConfig();
const apiTokenStore = new FakeApiTokenStore();
const environmentStore = new FakeEnvironmentStore();

const eventService = createFakeEventsService(config);

const tokenService = new ApiTokenService(
{ apiTokenStore, environmentStore },
config,
eventService,
);
const token = await tokenService.createApiTokenWithProjects(
const { apiTokenService } = createFakeApiTokenService(config);
const token = await apiTokenService.createApiTokenWithProjects(
{
environment: '*',
projects: ['*'],
Expand All @@ -191,7 +137,7 @@ test('getUserForToken should get a user with admin token user id and token name'
extractAuditInfoFromUser(ADMIN_TOKEN_USER as IUser),
);

const user = await tokenService.getUserForToken(token.secret);
const user = await apiTokenService.getUserForToken(token.secret);
expect(user).toBeDefined();
expect(user!.username).toBe(token.tokenName);
expect(user!.internalAdminTokenUserId).toBe(ADMIN_TOKEN_USER.id);
Expand All @@ -209,14 +155,8 @@ describe('API token getTokenWithCache', () => {

const setup = (options?: IUnleashOptions) => {
const config: IUnleashConfig = createTestConfig(options);
const apiTokenStore = new FakeApiTokenStore();
const environmentStore = new FakeEnvironmentStore();

const apiTokenService = new ApiTokenService(
{ apiTokenStore, environmentStore },
config,
createFakeEventsService(config),
);
const { apiTokenService, apiTokenStore } =
createFakeApiTokenService(config);
return {
apiTokenService,
apiTokenStore,
Expand Down
8 changes: 7 additions & 1 deletion src/lib/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ import { FeatureLifecycleService } from '../features/feature-lifecycle/feature-l
import { createFakeFeatureLifecycleService } from '../features/feature-lifecycle/createFeatureLifecycle';
import { FeatureLifecycleReadModel } from '../features/feature-lifecycle/feature-lifecycle-read-model';
import { FakeFeatureLifecycleReadModel } from '../features/feature-lifecycle/fake-feature-lifecycle-read-model';
import {
createApiTokenService,
createFakeApiTokenService,
} from '../features/api-tokens/createApiTokenService';

export const createServices = (
stores: IUnleashStores,
Expand All @@ -144,7 +148,9 @@ export const createServices = (
groupService,
eventService,
);
const apiTokenService = new ApiTokenService(stores, config, eventService);
const apiTokenService = db
? createApiTokenService(db, config)
: createFakeApiTokenService(config).apiTokenService;
const lastSeenService = db
? createLastSeenService(db, config)
: createFakeLastSeenService(config);
Expand Down
Loading

0 comments on commit 6d91380

Please sign in to comment.