Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EW-998 created a new sync Module #5222

Merged
merged 17 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions apps/server/src/infra/sync/console/sync.console.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { ConsoleWriterService } from '@src/infra/console';
import { createMock } from '@golevelup/ts-jest';
import { Test } from '@nestjs/testing';
import { Logger } from '@src/core/logger';
import { SyncUc } from '../uc/sync.uc';
import { SyncConsole } from './sync.console';

describe(SyncConsole.name, () => {
let syncConsole: SyncConsole;
let syncUc: SyncUc;

beforeAll(async () => {
const module = await Test.createTestingModule({
providers: [
SyncConsole,
{
provide: ConsoleWriterService,
useValue: createMock<ConsoleWriterService>(),
},
{
provide: SyncUc,
useValue: createMock<SyncUc>(),
},
{
provide: Logger,
useValue: createMock<Logger>(),
},
],
}).compile();

syncConsole = module.get(SyncConsole);
syncUc = module.get(SyncUc);
});

describe('when sync console is initialized', () => {
it('should be defined', () => {
expect(syncConsole).toBeDefined();
});
});

describe('startSync', () => {
const setup = () => {
const target = 'tsp';
return { target };
};

it('should call startSync method of syncUc', async () => {
const { target } = setup();
await syncConsole.startSync(target);
expect(syncUc.startSync).toHaveBeenCalledWith(target);
});
});
});
24 changes: 24 additions & 0 deletions apps/server/src/infra/sync/console/sync.console.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Logger } from '@src/core/logger';
import { Command, Console } from 'nestjs-console';
import { ConsoleWriterService } from '@infra/console';
import { SyncUc } from '../uc/sync.uc';

@Console({ command: 'sync', description: 'Prefixes all synchronization related console commands.' })
export class SyncConsole {
constructor(
private consoleWriter: ConsoleWriterService,
Fshmit marked this conversation as resolved.
Show resolved Hide resolved
private readonly syncUc: SyncUc,
private readonly logger: Logger
) {
this.logger.setContext(SyncConsole.name);
}

// add promise and await to the method
Fshmit marked this conversation as resolved.
Show resolved Hide resolved
@Command({
command: 'start <target>',
Fshmit marked this conversation as resolved.
Show resolved Hide resolved
description: 'Starts the synchronization process.',
})
async startSync(target: string): Promise<void> {
Fshmit marked this conversation as resolved.
Show resolved Hide resolved
await this.syncUc.startSync(target);
}
}
67 changes: 67 additions & 0 deletions apps/server/src/infra/sync/service/sync.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Test, TestingModule } from '@nestjs/testing';
import { createMock } from '@golevelup/ts-jest';
import { faker } from '@faker-js/faker';
import { SyncService } from './sync.service';
import { TspSyncStrategy } from '../tsp/tsp-sync.strategy';
import { SyncStrategyTarget } from '../sync-strategy.types';

describe(SyncService.name, () => {
let module: TestingModule;
let service: SyncService;

beforeAll(async () => {
module = await Test.createTestingModule({
providers: [
SyncService,
{
provide: TspSyncStrategy,
useValue: createMock<TspSyncStrategy>({
getType(): SyncStrategyTarget {
return SyncStrategyTarget.TSP;
},
sync(): Promise<void> {
return Promise.resolve();
},
}),
},
],
}).compile();

service = module.get(SyncService);
});

afterAll(async () => {
await module.close();
});

describe('when sync service is initialized', () => {
it('should be defined', () => {
expect(service).toBeDefined();
});
});

describe('startSync', () => {
const setup = () => {
Fshmit marked this conversation as resolved.
Show resolved Hide resolved
const error = new Error('please provide a valid target strategy name to start its synchronization process');
const invalidSystem = faker.lorem.word();
const validSystem = 'tsp';

return { error, invalidSystem, validSystem };
};
describe('when provided target is invalid', () => {
it('should throw an invalid provided target error', async () => {
const { error, invalidSystem } = setup();
await expect(service.startSync(invalidSystem)).rejects.toThrowError(error);
});
});

describe('when provided target is valid', () => {
it('should call sync method of the provided target', async () => {
const { validSystem } = setup();
const syncSpy = jest.spyOn(service.strategies.get(validSystem as SyncStrategyTarget)!, 'sync');
Fshmit marked this conversation as resolved.
Show resolved Hide resolved
await service.startSync(validSystem);
expect(syncSpy).toHaveBeenCalled();
});
});
});
});
26 changes: 26 additions & 0 deletions apps/server/src/infra/sync/service/sync.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Injectable } from '@nestjs/common';
import { TspSyncStrategy } from '../tsp/tsp-sync.strategy';
import { SyncStrategy } from '../strategy/sync-strategy';
import { SyncStrategyTarget } from '../sync-strategy.types';

@Injectable()
export class SyncService {
strategies: Map<SyncStrategyTarget, SyncStrategy> = new Map<SyncStrategyTarget, SyncStrategy>();
Fshmit marked this conversation as resolved.
Show resolved Hide resolved

constructor(private readonly tspSyncStrategy: TspSyncStrategy) {
this.registerStrategy(tspSyncStrategy);
}

protected registerStrategy(strategy: SyncStrategy) {
this.strategies.set(strategy.getType(), strategy);
}

public async startSync(target: string): Promise<void> {
const targetStrategy = target as SyncStrategyTarget;
mkreuzkam-cap marked this conversation as resolved.
Show resolved Hide resolved
if (!this.strategies.has(targetStrategy)) {
throw new Error('please provide a valid target strategy name to start its synchronization process');
Fshmit marked this conversation as resolved.
Show resolved Hide resolved
}
// Assuming there is an async operation to be awaited
Fshmit marked this conversation as resolved.
Show resolved Hide resolved
await this.strategies.get(targetStrategy)?.sync();
}
}
7 changes: 7 additions & 0 deletions apps/server/src/infra/sync/strategy/sync-strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { SyncStrategyTarget } from '../sync-strategy.types';

export abstract class SyncStrategy {
abstract getType(): SyncStrategyTarget;

abstract sync(): Promise<void>;
}
3 changes: 3 additions & 0 deletions apps/server/src/infra/sync/sync-strategy.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum SyncStrategyTarget {
TSP = 'tsp',
}
14 changes: 14 additions & 0 deletions apps/server/src/infra/sync/sync.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { LoggerModule } from '@src/core/logger';
import { ConsoleWriterModule } from '@infra/console';
import { SyncConsole } from './console/sync.console';
import { SyncUc } from './uc/sync.uc';
import { SyncService } from './service/sync.service';
import { TspSyncStrategy } from './tsp/tsp-sync.strategy';

@Module({
imports: [LoggerModule, ConsoleWriterModule],
providers: [SyncConsole, SyncUc, SyncService, TspSyncStrategy],
exports: [SyncConsole],
})
export class SyncModule {}
39 changes: 39 additions & 0 deletions apps/server/src/infra/sync/tsp/tsp-sync.strategy.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { TestingModule, Test } from '@nestjs/testing';
import { SyncStrategyTarget } from '../sync-strategy.types';
import { TspSyncStrategy } from './tsp-sync.strategy';

describe(TspSyncStrategy.name, () => {
let module: TestingModule;
let strategy: TspSyncStrategy;

beforeAll(async () => {
module = await Test.createTestingModule({
providers: [TspSyncStrategy],
}).compile();

strategy = module.get(TspSyncStrategy);
});

afterAll(async () => {
await module.close();
});

describe('when tsp sync strategy is initialized', () => {
it('should be defined', () => {
expect(strategy).toBeDefined();
});
});

describe('getType', () => {
it('should return tsp', () => {
Fshmit marked this conversation as resolved.
Show resolved Hide resolved
expect(strategy.getType()).toBe(SyncStrategyTarget.TSP);
});
});

describe('sync', () => {
it('should return a promise', async () => {

Check failure on line 34 in apps/server/src/infra/sync/tsp/tsp-sync.strategy.spec.ts

View workflow job for this annotation

GitHub Actions / nest_lint

Async arrow function has no 'await' expression
const result = strategy.sync();
expect(result).toBeInstanceOf(Promise);
});
});
});
15 changes: 15 additions & 0 deletions apps/server/src/infra/sync/tsp/tsp-sync.strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Injectable } from '@nestjs/common';
import { SyncStrategy } from '../strategy/sync-strategy';
import { SyncStrategyTarget } from '../sync-strategy.types';

@Injectable()
export class TspSyncStrategy extends SyncStrategy {
getType(): SyncStrategyTarget {
return SyncStrategyTarget.TSP;
}

sync(): Promise<void> {
// implementation
return Promise.resolve();
}
}
46 changes: 46 additions & 0 deletions apps/server/src/infra/sync/uc/sync.uc.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Test, TestingModule } from '@nestjs/testing';
import { createMock } from '@golevelup/ts-jest';
import { SyncUc } from './sync.uc';
import { SyncService } from '../service/sync.service';

describe(SyncUc.name, () => {
let module: TestingModule;
let uc: SyncUc;
let service: SyncService;

beforeAll(async () => {
module = await Test.createTestingModule({
providers: [
SyncUc,
{
provide: SyncService,
useValue: createMock<SyncService>({}),
},
],
}).compile();
uc = module.get(SyncUc);
service = module.get(SyncService);
});

describe('when sync uc is initialized', () => {
it('should be defined', () => {
expect(uc).toBeDefined();
});
});

describe('startSync', () => {
const setup = () => {
const validTarget = 'tsp';

return { validTarget };
};

describe('when calling startSync', () => {
it('should call sync method', async () => {
const { validTarget } = setup();
await uc.startSync(validTarget);
expect(service.startSync).toHaveBeenCalledWith(validTarget);
});
});
});
});
11 changes: 11 additions & 0 deletions apps/server/src/infra/sync/uc/sync.uc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Injectable } from '@nestjs/common';
import { SyncService } from '../service/sync.service';

@Injectable()
export class SyncUc {
constructor(private readonly syncService: SyncService) {}

public async startSync(target: string): Promise<void> {
await this.syncService.startSync(target);
}
}
2 changes: 2 additions & 0 deletions apps/server/src/modules/server/server.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export interface ServerConfig
SCHULCONNEX_CLIENT__CLIENT_SECRET: string | undefined;
FEATURE_AI_TUTOR_ENABLED: boolean;
FEATURE_ROOMS_ENABLED: boolean;
FEATURE_TSP_SYNC_ENABLED: boolean;
}

const config: ServerConfig = {
Expand Down Expand Up @@ -197,6 +198,7 @@ const config: ServerConfig = {
FEATURE_IDENTITY_MANAGEMENT_ENABLED: Configuration.get('FEATURE_IDENTITY_MANAGEMENT_ENABLED') as boolean,
FEATURE_IDENTITY_MANAGEMENT_STORE_ENABLED: Configuration.get('FEATURE_IDENTITY_MANAGEMENT_STORE_ENABLED') as boolean,
FEATURE_IDENTITY_MANAGEMENT_LOGIN_ENABLED: Configuration.get('FEATURE_IDENTITY_MANAGEMENT_LOGIN_ENABLED') as boolean,
FEATURE_TSP_SYNC_ENABLED: Configuration.get('FEATURE_TSP_SYNC_ENABLED') as boolean,
STUDENT_TEAM_CREATION: Configuration.get('STUDENT_TEAM_CREATION') as string,
SYNCHRONIZATION_CHUNK: Configuration.get('SYNCHRONIZATION_CHUNK') as number,
// parse [<description>:]<token>,[<description>:]<token>... and discard description
Expand Down
5 changes: 5 additions & 0 deletions config/default.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@
"default": false,
"description": "Feature toggle for TSP features."
},
"FEATURE_TSP_SYNC_ENABLED": {
"type": "boolean",
"default": false,
"description": "Feature toggle for TSP sync."
},
"BLOCK_DISPOSABLE_EMAIL_DOMAINS": {
"type": "boolean",
"default": true,
Expand Down
Loading