diff --git a/packages/execution/test/models/Application.spec.ts b/packages/execution/test/models/Application.spec.ts new file mode 100644 index 00000000..0bd30918 --- /dev/null +++ b/packages/execution/test/models/Application.spec.ts @@ -0,0 +1,35 @@ + +import { describe, expect, it } from 'vitest'; + +import { APPLICATIONS } from './fixtures'; + +const application = APPLICATIONS.GENERAL; + +describe('models/Application', () => +{ + // TODO: Add classes tests + + describe('.getProcedureNames()', () => + { + it('should contain all public and protected procedure names', () => + { + const procedureNames = application.getProcedureNames(); + + expect(procedureNames).toHaveLength(2); + expect(procedureNames).toContain('protected'); + expect(procedureNames).toContain('public'); + }); + }); + + describe('.hasProcedure(name)', () => + { + it('should have public procedures', () => + { + const hasProtectedProcedure = application.hasProcedure('protected'); + const hasPublicProcedure = application.hasProcedure('public'); + + expect(hasProtectedProcedure).toBeTruthy(); + expect(hasPublicProcedure).toBeTruthy(); + }); + }); +}); diff --git a/packages/execution/test/models/fixtures/applications.fixture.ts b/packages/execution/test/models/fixtures/applications.fixture.ts new file mode 100644 index 00000000..b994ec37 --- /dev/null +++ b/packages/execution/test/models/fixtures/applications.fixture.ts @@ -0,0 +1,13 @@ + +import Application from '../../../src/models/Application'; + +import { SEGMENTS } from './segments.fixture'; + +const generalApplication = new Application(); +generalApplication.addSegment(SEGMENTS.GENERAL); +// TODO: Add more segments + +export const APPLICATIONS = +{ + GENERAL: generalApplication +}; diff --git a/packages/execution/test/models/fixtures/index.ts b/packages/execution/test/models/fixtures/index.ts index 46b72800..a007d30e 100644 --- a/packages/execution/test/models/fixtures/index.ts +++ b/packages/execution/test/models/fixtures/index.ts @@ -1,4 +1,5 @@ +export * from './applications.fixture'; export * from './executables.fixture'; export * from './implementations.fixture'; export * from './parameters.fixture'; diff --git a/packages/logging/src/Logger.ts b/packages/logging/src/Logger.ts index 47c97a98..20b75d83 100644 --- a/packages/logging/src/Logger.ts +++ b/packages/logging/src/Logger.ts @@ -6,7 +6,7 @@ export default class Logger #debugEnabled: boolean; #writer: Writer; - constructor(writer: Writer = console, debugEnabled: boolean = false) + constructor(debugEnabled: boolean = false, writer: Writer = console) { this.#debugEnabled = debugEnabled; this.#writer = writer; diff --git a/packages/logging/test/Logger.spec.ts b/packages/logging/test/Logger.spec.ts index 1e971228..cbfcce9c 100644 --- a/packages/logging/test/Logger.spec.ts +++ b/packages/logging/test/Logger.spec.ts @@ -10,44 +10,27 @@ beforeEach(() => writer.clear(); }); -const logger = new Logger(writer); - describe('Logger', () => { - describe('.debug(...messages))', () => + describe('Message creation', () => { - it('should log messages when debug is enabled', () => - { - const DebugLogger = new Logger(writer, true); + const logger = new Logger(false, writer); - DebugLogger.debug('message'); - expect(writer.lastMessage).toEqual('[DEBUG] message'); - }); - - it('should not log messages when debug is disabled', () => + it('should log [CATEGORY] messages', () => { - logger.debug('message'); - expect(writer.lastMessage).toEqual(undefined); + logger.info('info'); + logger.warn('warn'); + logger.error('error'); + logger.fatal('fatal'); + + expect(writer.messages).toEqual([ + '[INFO] info', + '[WARN] warn', + '[ERROR] error', + '[FATAL] fatal' + ]); }); - }); - - it('log [CATEGORY] messages', () => - { - logger.info('info'); - logger.warn('warn'); - logger.error('error'); - logger.fatal('fatal'); - - expect(writer.messages).toEqual([ - '[INFO] info', - '[WARN] warn', - '[ERROR] error', - '[FATAL] fatal' - ]); - }); - - describe('.#createMessage()', () => - { + it('should format string message', () => { logger.info(INPUT.STRING); @@ -123,4 +106,25 @@ describe('Logger', () => expect(writer.lastMessage).toEqual(OUTPUT.ERROR_WITH_STACKTRACE); }); }); + + describe('Debug mode', () => + { + it('should log messages when debug is enabled', () => + { + const logger = new Logger(true, writer); + + logger.debug('message'); + + expect(writer.lastMessage).toEqual('[DEBUG] message'); + }); + + it('should not log messages when debug is disabled', () => + { + const logger = new Logger(false, writer); + + logger.debug('message'); + + expect(writer.lastMessage).toEqual(undefined); + }); + }); }); diff --git a/packages/runtime/test/services/Runtime.spec.ts b/packages/runtime/test/Runtime.spec.ts similarity index 97% rename from packages/runtime/test/services/Runtime.spec.ts rename to packages/runtime/test/Runtime.spec.ts index c2267e66..e8afc380 100644 --- a/packages/runtime/test/services/Runtime.spec.ts +++ b/packages/runtime/test/Runtime.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; -import { RUNTIMES } from '../_fixtures/services/Runtime.fixture'; +import { RUNTIMES } from './_fixtures/services/Runtime.fixture'; const goodRuntime = RUNTIMES.GOOD; const badRuntime = RUNTIMES.BAD; diff --git a/packages/runtime/test/_fixtures/interfaces/FileManager.fixture.ts b/packages/runtime/test/_fixtures/interfaces/FileManager.fixture.ts deleted file mode 100644 index 2c2f9914..00000000 --- a/packages/runtime/test/_fixtures/interfaces/FileManager.fixture.ts +++ /dev/null @@ -1,75 +0,0 @@ - -import FileManager from '../../../src/interfaces/FileManager'; -import File from '../../../src/models/File'; - -class TestFileManager implements FileManager -{ - getRootLocation(): string - { - return ''; - } - - getAbsoluteLocation(filename: string): string - { - return filename; - } - - getRelativeLocation(filename: string): string - { - return filename; - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async getType(filename: string): Promise - { - return 'file'; - } - - async getContent(filename: string): Promise - { - switch (filename) - { - case 'private.local.js': - return Buffer.from('private()'); - - case 'first.local.js': - return Buffer.from('first()'); - - case 'fourth.remote.js': - return Buffer.from('fourth()'); - - case 'index.html': - return Buffer.from('

Hello world

'); - } - - return Buffer.from(''); - } - - async read(filename: string): Promise - { - const type = await this.getType(filename); - const content = await this.getContent(filename); - - return new File(filename, type, content); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async write(filename: string, content: string): Promise - { - // Do nothing - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async delete(filename: string): Promise - { - // Do nothing - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async filter(pattern: string): Promise - { - return []; - } -} - -export { TestFileManager }; diff --git a/packages/runtime/test/_fixtures/interfaces/HealthCheck.fixture.ts b/packages/runtime/test/_fixtures/interfaces/HealthCheck.fixture.ts deleted file mode 100644 index c438fb1d..00000000 --- a/packages/runtime/test/_fixtures/interfaces/HealthCheck.fixture.ts +++ /dev/null @@ -1,70 +0,0 @@ - -import HealthCheck from '../../../src/interfaces/HealthCheck'; - -class HealthyCheck implements HealthCheck -{ - get name() { return 'good'; } - - get timeout() { return undefined; } - - async isHealthy(): Promise { return true; } -} - -class UnhealthyCheck implements HealthCheck -{ - get name() { return 'bad'; } - - get timeout() { return undefined; } - - async isHealthy(): Promise { return false; } -} - -class ErrorHealthCheck implements HealthCheck -{ - get name() { return 'error'; } - - get timeout() { return undefined; } - - async isHealthy(): Promise { throw new Error('ErrorHealthCheck'); } -} - -class TimedOutHealthCheck implements HealthCheck -{ - get name() { return 'timedOut'; } - - get timeout() { return 50; } - - async isHealthy(): Promise - { - return new Promise((resolve) => - { - setTimeout(resolve, 5000); - }).then(() => true); - } -} - -class InTimeHealthCheck implements HealthCheck -{ - get name() { return 'inTime'; } - - get timeout() { return 5000; } - - async isHealthy(): Promise - { - return new Promise((resolve) => - { - setTimeout(resolve, 50); - }).then(() => true); - } -} - -const HEALTH_CHECKS = -{ - GOOD: new HealthyCheck(), - BAD: new UnhealthyCheck(), - ERROR: new ErrorHealthCheck(), - TIMEDOUT: new TimedOutHealthCheck(), - INTIME: new InTimeHealthCheck() -}; - -export { HEALTH_CHECKS }; diff --git a/packages/runtime/test/_fixtures/interfaces/Middleware.fixture.ts b/packages/runtime/test/_fixtures/interfaces/Middleware.fixture.ts deleted file mode 100644 index 388812f4..00000000 --- a/packages/runtime/test/_fixtures/interfaces/Middleware.fixture.ts +++ /dev/null @@ -1,55 +0,0 @@ - -import Request from '../../../src/models/Request'; -import Response from '../../../src/models/Response'; -import Middleware from '../../../src/interfaces/Middleware'; - -class FirstMiddleware implements Middleware -{ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async handle(request: Request, next: () => Promise): Promise - { - request.setHeader('first', 'yes'); - request.setHeader('last', '1'); - - const response = await next(); - response.result = '1' + response.result; - - return response; - } -} - -class SecondMiddleware implements Middleware -{ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async handle(request: Request, next: () => Promise): Promise - { - request.setHeader('second', 'yes'); - request.setHeader('last', '2'); - - const response = await next(); - response.result = '2' + response.result; - - return response; - } -} - -class ThirdMiddleware implements Middleware -{ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async handle(request: Request, next: () => Promise): Promise - { - request.setHeader('third', 'yes'); - request.setHeader('last', '3'); - - return new Response('3'); - } -} - -const MIDDLEWARES = -{ - FIRST: new FirstMiddleware(), - SECOND: new SecondMiddleware(), - THIRD: new ThirdMiddleware() -}; - -export { MIDDLEWARES }; diff --git a/packages/runtime/test/_fixtures/services/LocalGateway.fixture.ts b/packages/runtime/test/_fixtures/services/LocalGateway.fixture.ts deleted file mode 100644 index 134071a5..00000000 --- a/packages/runtime/test/_fixtures/services/LocalGateway.fixture.ts +++ /dev/null @@ -1,32 +0,0 @@ - -import LocalGateway from '../../../src/services/LocalGateway'; - -import { REPOSITORIES } from './LocalRepository.fixture'; -import { WORKERS } from './LocalWorker.fixture'; - -const GATEWAY_URL = 'http://localhost:80'; - -const standaloneGateway = new LocalGateway(REPOSITORIES.DUMMY, GATEWAY_URL); -standaloneGateway.addWorker(WORKERS.SINGLE); - -const distributedGateway = new LocalGateway(REPOSITORIES.DUMMY, GATEWAY_URL); -distributedGateway.addWorker(WORKERS.FIRST); -distributedGateway.addWorker(WORKERS.SECOND); - -const healthGateway = new LocalGateway(REPOSITORIES.DUMMY, GATEWAY_URL); -healthGateway.addWorker(WORKERS.GOOD); -healthGateway.addWorker(WORKERS.BAD); - -const protectedGateway = new LocalGateway(REPOSITORIES.DUMMY, GATEWAY_URL, 'MY_PROTECTED_ACCESS_KEY'); -protectedGateway.addWorker(WORKERS.FIRST, 'MY_PROTECTED_ACCESS_KEY'); -protectedGateway.addWorker(WORKERS.SECOND); - -const GATEWAYS = -{ - STANDALONE: standaloneGateway, - DISTRIBUTED: distributedGateway, - HEALTH: healthGateway, - PROTECTED: protectedGateway -}; - -export { GATEWAYS, GATEWAY_URL }; diff --git a/packages/runtime/test/_fixtures/services/LocalRepository.fixture.ts b/packages/runtime/test/_fixtures/services/LocalRepository.fixture.ts deleted file mode 100644 index 2344f353..00000000 --- a/packages/runtime/test/_fixtures/services/LocalRepository.fixture.ts +++ /dev/null @@ -1,33 +0,0 @@ - -import DummyRepository from '../../../src/services/DummyRepository'; -import LocalRepository from '../../../src/services/LocalRepository'; - -import { TestFileManager } from '../interfaces/FileManager.fixture'; -import { SEGMENT_FILES } from '../models/Segment.fixture'; - -const defaultRepository = new LocalRepository(new TestFileManager()); -defaultRepository.assets = new Set(['index.html']); - -await defaultRepository.registerSegment('first', SEGMENT_FILES.FIRST); -await defaultRepository.registerSegment('second', SEGMENT_FILES.SECOND); - -const dummyRepository = new DummyRepository(); - -const REPOSITORIES = -{ - DEFAULT: defaultRepository, - DUMMY: dummyRepository -}; - -Object.freeze(REPOSITORIES); - -const REPOSITORY_FILES = -{ - UNSEGMENTED: SEGMENT_FILES.GENERAL[0], - LOCAL: SEGMENT_FILES.FIRST[0], - REMOTE: SEGMENT_FILES.SECOND[0] -}; - -Object.freeze(REPOSITORY_FILES); - -export { REPOSITORIES, REPOSITORY_FILES }; diff --git a/packages/runtime/test/_fixtures/services/LocalWorker.fixture.ts b/packages/runtime/test/_fixtures/services/LocalWorker.fixture.ts deleted file mode 100644 index f922e162..00000000 --- a/packages/runtime/test/_fixtures/services/LocalWorker.fixture.ts +++ /dev/null @@ -1,39 +0,0 @@ - -import LocalWorker from '../../../src/services/LocalWorker'; -import { setRuntime } from '../../../src/hooks'; - -import { HEALTH_CHECKS } from '../interfaces/HealthCheck.fixture'; -import { SEGMENTS } from '../models/Segment.fixture'; -import { REPOSITORIES } from './LocalRepository.fixture'; - -const TRUST_KEY = 'MY_TRUST_KEY'; - -const singleWorker = new LocalWorker(REPOSITORIES.DUMMY, undefined, undefined, TRUST_KEY); -singleWorker.addSegment(SEGMENTS.GENERAL); -singleWorker.addSegment(SEGMENTS.FIRST); -singleWorker.addSegment(SEGMENTS.SECOND); - -const firstWorker = new LocalWorker(REPOSITORIES.DUMMY); -firstWorker.addSegment(SEGMENTS.FIRST); - -const secondWorker = new LocalWorker(REPOSITORIES.DUMMY); -secondWorker.addSegment(SEGMENTS.SECOND); - -const goodWorker = new LocalWorker(REPOSITORIES.DUMMY); -goodWorker.addHealthCheck(HEALTH_CHECKS.GOOD); - -const badWorker = new LocalWorker(REPOSITORIES.DUMMY); -badWorker.addHealthCheck(HEALTH_CHECKS.BAD); - -const WORKERS = -{ - SINGLE: singleWorker, - FIRST: firstWorker, - SECOND: secondWorker, - GOOD: goodWorker, - BAD: badWorker -}; - -setRuntime(singleWorker); - -export { WORKERS, TRUST_KEY }; diff --git a/packages/runtime/test/_fixtures/services/ProcedureRuntime.fixture.ts b/packages/runtime/test/_fixtures/services/ProcedureRuntime.fixture.ts deleted file mode 100644 index 6459c6d8..00000000 --- a/packages/runtime/test/_fixtures/services/ProcedureRuntime.fixture.ts +++ /dev/null @@ -1,35 +0,0 @@ - -import Request from '../../../src/models/Request'; -import Response from '../../../src/models/Response'; - -import ProcedureRuntime from '../../../src/services/ProcedureRuntime'; - -import { MIDDLEWARES } from '../interfaces/Middleware.fixture'; -import { REPOSITORIES } from './LocalRepository.fixture'; - -class MiddlewareRuntime extends ProcedureRuntime -{ - getProcedureNames(): string[] { return []; } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - hasProcedure(name: string): boolean { return false; } - - async start() { } - - async stop() { } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async run(request: Request): Promise { return new Response(); } -} - -const middlewareRuntime = new MiddlewareRuntime(REPOSITORIES.DUMMY); -middlewareRuntime.addMiddleware(MIDDLEWARES.FIRST); -middlewareRuntime.addMiddleware(MIDDLEWARES.SECOND); -middlewareRuntime.addMiddleware(MIDDLEWARES.THIRD); - -const RUNTIMES = -{ - MIDDLEWARE: middlewareRuntime -}; - -export { RUNTIMES }; diff --git a/packages/runtime/test/_fixtures/services/Remote.fixture.ts b/packages/runtime/test/_fixtures/services/Remote.fixture.ts deleted file mode 100644 index 72a58b6c..00000000 --- a/packages/runtime/test/_fixtures/services/Remote.fixture.ts +++ /dev/null @@ -1,42 +0,0 @@ - -import Request from '../../../src/models/Request'; -import Version from '../../../src/models/Version'; -import Remote from '../../../src/services/Remote'; - -const remote = new Remote('http://localhost:3000'); -const CONTENT_TYPE = 'Content-Type'; - -const BOOLEAN_REQUEST = new Request('game/checkSecret', Version.DEFAULT, new Map(), new Map()); -const NUMBER_REQUEST = new Request('game/getSecret', Version.DEFAULT, new Map(), new Map()); -const OBJECT_REQUEST = new Request('game/scoreSecret', Version.DEFAULT, new Map(), new Map()); -const DEFAULT_REQUEST = new Request('game/guessSecret', Version.DEFAULT, new Map(), new Map()); - -const BOOLEAN_RESPONSE = new Response('false', { status: 200, statusText: 'OK', headers: { 'Content-Type': 'application/boolean' } }); -const NUMBER_RESPONSE = new Response('42', { status: 200, statusText: 'OK', headers: { 'Content-Type': 'application/number' } }); -const OBJECT_RESPONSE = new Response('{"result":42}', { status: 200, statusText: 'OK', headers: { 'Content-Type': 'application/json' } }); -const DEFAULT_RESPONSE = new Response('Sorry, try again', { status: 200, statusText: 'OK', headers: { 'Content-Type': 'text/plain' } }); - -const REQUESTS = { - BOOLEAN_REQUEST: BOOLEAN_REQUEST, - NUMBER_REQUEST: NUMBER_REQUEST, - OBJECT_REQUEST: OBJECT_REQUEST, - DEFAULT_REQUEST: DEFAULT_REQUEST -}; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -function customFetch(input: URL | RequestInfo, options: RequestInit | undefined): Promise -{ - const url = input.toString(); - - switch (url) - { - case 'http://localhost:3000/rpc/game/checkSecret?version=0.0.0&serialize=true': return Promise.resolve(BOOLEAN_RESPONSE); - case 'http://localhost:3000/rpc/game/getSecret?version=0.0.0&serialize=true': return Promise.resolve(NUMBER_RESPONSE); - case 'http://localhost:3000/rpc/game/scoreSecret?version=0.0.0&serialize=true': return Promise.resolve(OBJECT_RESPONSE); - default: return Promise.resolve(DEFAULT_RESPONSE); - } -} - -globalThis.fetch = customFetch; - -export { REQUESTS, remote, CONTENT_TYPE }; diff --git a/packages/runtime/test/_fixtures/services/RemoteWorker.fixture.ts b/packages/runtime/test/_fixtures/services/RemoteWorker.fixture.ts deleted file mode 100644 index 1fd453a1..00000000 --- a/packages/runtime/test/_fixtures/services/RemoteWorker.fixture.ts +++ /dev/null @@ -1,14 +0,0 @@ - -import RemoteWorker from '../../../src/services/RemoteWorker'; - -const WORKER_URL = 'http://localhost:80'; - -const remoteWorker = new RemoteWorker(WORKER_URL); -remoteWorker.procedureNames = new Set(['first', 'second']); - -const WORKERS = -{ - REMOTE: remoteWorker -}; - -export { WORKERS, WORKER_URL }; diff --git a/packages/runtime/test/_fixtures/services/WorkerBalancer.fixture.ts b/packages/runtime/test/_fixtures/services/WorkerBalancer.fixture.ts deleted file mode 100644 index fb12b1aa..00000000 --- a/packages/runtime/test/_fixtures/services/WorkerBalancer.fixture.ts +++ /dev/null @@ -1,23 +0,0 @@ - -import LocalWorker from '../../../src/services/LocalWorker'; -import WorkerBalancer from '../../../src/services/WorkerBalancer'; - -import { REPOSITORIES } from './LocalRepository.fixture'; - -const WORKERS = -{ - FIRST: new LocalWorker(REPOSITORIES.DUMMY), - SECOND: new LocalWorker(REPOSITORIES.DUMMY) -}; - -const filledBalancer = new WorkerBalancer(); -filledBalancer.addWorker(WORKERS.FIRST); -filledBalancer.addWorker(WORKERS.SECOND); - -const BALANCERS = -{ - FILLED: filledBalancer, - EMPTY: new WorkerBalancer() -}; - -export { BALANCERS, WORKERS }; diff --git a/packages/runtime/test/_fixtures/services/WorkerMonitor.fixture.ts b/packages/runtime/test/_fixtures/services/WorkerMonitor.fixture.ts deleted file mode 100644 index fb15ce67..00000000 --- a/packages/runtime/test/_fixtures/services/WorkerMonitor.fixture.ts +++ /dev/null @@ -1,14 +0,0 @@ - -import WorkerMonitor from '../../../src/services/WorkerMonitor'; - -import { WORKERS } from './LocalWorker.fixture'; -import { GATEWAYS } from './LocalGateway.fixture'; - -const GATEWAY = GATEWAYS.HEALTH; - -const MONITORS = -{ - HEALTH: new WorkerMonitor(GATEWAY, 100) -}; - -export { MONITORS, GATEWAY, WORKERS }; diff --git a/packages/runtime/test/_fixtures/services/Runtime.fixture.ts b/packages/runtime/test/fixtures/Runtime.fixture.ts similarity index 100% rename from packages/runtime/test/_fixtures/services/Runtime.fixture.ts rename to packages/runtime/test/fixtures/Runtime.fixture.ts diff --git a/packages/runtime/test/services/LocalGateway.spec.ts b/packages/runtime/test/services/LocalGateway.spec.ts deleted file mode 100644 index 59b96b2c..00000000 --- a/packages/runtime/test/services/LocalGateway.spec.ts +++ /dev/null @@ -1,108 +0,0 @@ - -import { describe, expect, it } from 'vitest'; - -import ProcedureNotFound from '../../src/errors/ProcedureNotFound'; -import InvalidTrustKey from '../../src/errors/InvalidTrustKey'; -import Request from '../../src/models/Request'; -import Version from '../../src/models/Version'; - -import { GATEWAYS, GATEWAY_URL } from '../_fixtures/services/LocalGateway.fixture'; - -const gateway = GATEWAYS.STANDALONE; - -describe('services/LocalGateway', () => -{ - describe('.url', () => - { - it('should contain an url', () => - { - expect(gateway.url).toContain(GATEWAY_URL); - }); - }); - - describe('.getProcedureNames()', () => - { - it('should contain all public and protected procedure names', () => - { - const procedureNames = gateway.getProcedureNames(); - - expect(procedureNames).toHaveLength(6); - expect(procedureNames).toContain('protected'); - expect(procedureNames).toContain('public'); - expect(procedureNames).toContain('second'); - expect(procedureNames).toContain('third'); - expect(procedureNames).toContain('fourth'); - expect(procedureNames).toContain('sixth'); - }); - }); - - describe('.hasProcedure(name)', () => - { - it('should have public procedures', () => - { - const hasProtectedProcedure = gateway.hasProcedure('protected'); - const hasPublicProcedure = gateway.hasProcedure('public'); - const hasSecondProcedure = gateway.hasProcedure('second'); - const hasThirdProcedure = gateway.hasProcedure('third'); - const hasFourthProcedure = gateway.hasProcedure('fourth'); - const hasSixthProcedure = gateway.hasProcedure('sixth'); - - expect(hasProtectedProcedure).toBeTruthy(); - expect(hasPublicProcedure).toBeTruthy(); - expect(hasSecondProcedure).toBeTruthy(); - expect(hasThirdProcedure).toBeTruthy(); - expect(hasFourthProcedure).toBeTruthy(); - expect(hasSixthProcedure).toBeTruthy(); - }); - }); - - describe('.run(name, version, parameters)', () => - { - it('should find and run a procedure from a worker', async () => - { - const request = new Request('second', Version.DEFAULT, new Map(), new Map()); - const response = await gateway.run(request); - - expect(response.result).toBe('first'); - }); - - it('should find and run a procedure from a worker that calls a procedure on another worker', async () => - { - const request = new Request('third', Version.DEFAULT, new Map(), new Map()); - const response = await gateway.run(request); - - expect(response.result).toBe('fourth'); - }); - - it('should not run a non-existing procedure', async () => - { - const request = new Request('nonExisting', Version.DEFAULT, new Map(), new Map()); - const run = async () => gateway.run(request); - - expect(run).rejects.toEqual(new ProcedureNotFound('nonExisting')); - }); - }); - - describe('.addWorker(worker, accessKey)', () => - { - it('should not add a worker with an incorrect access key', async () => - { - const worker = gateway.workers[0]; - const protectedGateway = GATEWAYS.PROTECTED; - - const addWorker = async () => protectedGateway.addWorker(worker, 'INCORRECT_ACCESS_KEY'); - - expect(addWorker).rejects.toEqual(new InvalidTrustKey()); - }); - - it('should not add a worker with an access key to an unprotected gateway', async () => - { - const worker = gateway.workers[0]; - const unprotectedGateway = GATEWAYS.STANDALONE; - - const addWorker = async () => unprotectedGateway.addWorker(worker, 'WORKER_ACCESS_KEY'); - - expect(addWorker).rejects.toEqual(new InvalidTrustKey()); - }); - }); -}); diff --git a/packages/runtime/test/services/LocalRepository.spec.ts b/packages/runtime/test/services/LocalRepository.spec.ts deleted file mode 100644 index 4274ec64..00000000 --- a/packages/runtime/test/services/LocalRepository.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ - -import { describe, expect, it } from 'vitest'; - -import FileNotFound from '../../src/errors/FileNotFound'; -import { REPOSITORIES, REPOSITORY_FILES } from '../_fixtures/services/LocalRepository.fixture'; - -const repository = REPOSITORIES.DEFAULT; - -describe('services/LocalRepository', () => -{ - describe('.readModule(clientId, filename)', () => - { - it('should return an unsegmented module file', async () => - { - const result = await repository.readModule(REPOSITORY_FILES.UNSEGMENTED, CLIENT.id); - - expect(result.content.toString()).toContain('private()'); - }); - - it('should return the actual module file', async () => - { - const result = await repository.readModule(REPOSITORY_FILES.LOCAL, CLIENT.id); - - expect(result.content).toContain('first()'); - }); - - it('should return a remote module file', async () => - { - const result = await repository.readModule(REPOSITORY_FILES.REMOTE, CLIENT.id); - - expect(result.content.toString()).toContain('fourth()'); - }); - - it('should return a public asset', async () => - { - const result = await repository.readAsset('index.html'); - - expect(result.content.toString()).toContain('

Hello world

'); - }); - - it('should not return a private asset', async () => - { - const run = async () => repository.readAsset('style.css'); - - expect(run).rejects.toEqual(new FileNotFound('style.css')); - }); - }); -}); diff --git a/packages/runtime/test/services/LocalWorker.spec.ts b/packages/runtime/test/services/LocalWorker.spec.ts deleted file mode 100644 index 9c4513db..00000000 --- a/packages/runtime/test/services/LocalWorker.spec.ts +++ /dev/null @@ -1,121 +0,0 @@ - -import { describe, expect, it } from 'vitest'; - -import ProcedureNotFound from '../../src/errors/ProcedureNotFound'; -import Request from '../../src/models/Request'; -import Version from '../../src/models/Version'; - -import { WORKERS, TRUST_KEY } from '../_fixtures/services/LocalWorker.fixture'; -import Unauthorized from '../../src/errors/generic/Unauthorized'; -import InvalidTrustKey from '../../src/errors/InvalidTrustKey'; - -const worker = WORKERS.SINGLE; - -describe('services/LocalWorker', () => -{ - describe('.isHealthy()', () => - { - it('should be healthy', async () => - { - const healthy = await worker.isHealthy(); - - expect(healthy).toBeTruthy(); - }); - }); - - describe('.hasProcedure(name)', () => - { - it('should find public procedures', () => - { - const hasSecondProcedure = worker.hasProcedure('second'); - const hasThirdProcedure = worker.hasProcedure('third'); - - expect(hasSecondProcedure).toBeTruthy(); - expect(hasThirdProcedure).toBeTruthy(); - }); - - it('should find protected procedures', () => - { - const hasProtectedProcedure = worker.hasProcedure('protected'); - - expect(hasProtectedProcedure).toBeTruthy(); - }); - - it('should not find private procedures', () => - { - const hasPrivateProcedure = worker.hasProcedure('private'); - const hasFirstProcedure = worker.hasProcedure('first'); - - expect(hasPrivateProcedure).toBeFalsy(); - expect(hasFirstProcedure).toBeFalsy(); - }); - - it('should not find non-existing procedures', () => - { - const hasNonExistingProcedure = worker.hasProcedure('nonExisting'); - - expect(hasNonExistingProcedure).toBeFalsy(); - }); - }); - - describe('.run(name, version, parameters)', () => - { - it('should run a public procedure that calls a private procedure on the same segment', async () => - { - const request = new Request('second', Version.DEFAULT, new Map(), new Map()); - const response = await worker.run(request); - - expect(response.result).toBe('first'); - }); - - it('should run a public procedure that calls a private procedure on another segment', async () => - { - const request = new Request('sixth', Version.DEFAULT, new Map(), new Map()); - const response = await worker.run(request); - - expect(response.result).toBe('first'); - }); - - it('should run a public procedure that calls a public procedure on another segment', async () => - { - const request = new Request('third', Version.DEFAULT, new Map(), new Map()); - const response = await worker.run(request); - - expect(response.result).toBe('fourth'); - }); - - it('should not run a non-existing procedure', async () => - { - const request = new Request('nonExisting', Version.DEFAULT, new Map(), new Map()); - const run = async () => worker.run(request); - - expect(run).rejects.toEqual(new ProcedureNotFound('nonExisting')); - }); - - it('should run a protected procedure with valid trust key', async () => - { - const headers = new Map().set('x-jitar-trust-key', TRUST_KEY); - const request = new Request('protected', Version.DEFAULT, new Map(), headers); - const response = await worker.run(request); - - expect(response.result).toBe('protected'); - }); - - it('should not run a protected procedure with invalid trust key', async () => - { - const headers = new Map().set('x-jitar-trust-key', 'invalid'); - const request = new Request('protected', Version.DEFAULT, new Map(), headers); - const run = async () => worker.run(request); - - expect(run).rejects.toEqual(new InvalidTrustKey()); - }); - - it('should not run a protected procedure without trust key', async () => - { - const request = new Request('protected', Version.DEFAULT, new Map(), new Map()); - const run = async () => worker.run(request); - - expect(run).rejects.toEqual(new Unauthorized()); - }); - }); -}); diff --git a/packages/runtime/test/services/ProcedureRuntime.spec.ts b/packages/runtime/test/services/ProcedureRuntime.spec.ts deleted file mode 100644 index 47afaf95..00000000 --- a/packages/runtime/test/services/ProcedureRuntime.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ - -import { describe, expect, it } from 'vitest'; - -import Request from '../../src/models/Request'; -import Version from '../../src/models/Version'; - -import { RUNTIMES } from '../_fixtures/services/ProcedureRuntime.fixture'; - -const runtime = RUNTIMES.MIDDLEWARE; - -describe('services/ProcedureRuntime', () => -{ - describe('.handle(fqn, version, args, headers', () => - { - it('should execute the middleware in the correct order', async () => - { - const args = new Map(); - const headers = new Map(); - - const request = new Request('test', new Version(1, 0, 0), args, headers); - const response = await runtime.handle(request); - - expect(response.result).toBe('123'); - expect(headers.get('first')).toBe('yes'); - expect(headers.get('second')).toBe('yes'); - expect(headers.get('third')).toBe('yes'); - expect(headers.get('last')).toBe('3'); // The last middleware to be called is the last one added - }); - }); -}); diff --git a/packages/runtime/test/services/Remote.spec.ts b/packages/runtime/test/services/Remote.spec.ts deleted file mode 100644 index aa384483..00000000 --- a/packages/runtime/test/services/Remote.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ - -import { describe, expect, it } from 'vitest'; - -import { REQUESTS, remote, CONTENT_TYPE } from '../_fixtures/services/Remote.fixture'; - -describe('services/Remote', () => -{ - describe('.run', () => - { - it('should return the response as boolean', async() => - { - const response = await remote.run(REQUESTS.BOOLEAN_REQUEST); - - expect(response.result).toBe(false); - expect(response.getHeader(CONTENT_TYPE)).toBe('application/boolean'); - }); - - it('should return the response as number', async() => - { - const response = await remote.run(REQUESTS.NUMBER_REQUEST); - - expect(response.result).toBe(42); - expect(response.getHeader(CONTENT_TYPE)).toBe('application/number'); - }); - - it('should return the response as object', async() => - { - const response = await remote.run(REQUESTS.OBJECT_REQUEST); - - expect(response.result).toEqual({ result: 42 }); - expect(response.getHeader(CONTENT_TYPE)).toBe('application/json'); - }); - - it('should return the response as string', async() => - { - const response = await remote.run(REQUESTS.DEFAULT_REQUEST); - - expect(response.result).toBe('Sorry, try again'); - expect(response.getHeader(CONTENT_TYPE)).toBe('text/plain'); - }); - }); -}); diff --git a/packages/runtime/test/services/RemoteWorker.spec.ts b/packages/runtime/test/services/RemoteWorker.spec.ts deleted file mode 100644 index b6174d2d..00000000 --- a/packages/runtime/test/services/RemoteWorker.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ - -import { describe, expect, it } from 'vitest'; - -import { WORKERS, WORKER_URL } from '../_fixtures/services/RemoteWorker.fixture'; - -const worker = WORKERS.REMOTE; - -describe('services/RemoteWorker', () => -{ - describe('.url', () => - { - it('should contain an url', () => - { - expect(worker.url).toContain(WORKER_URL); - }); - }); - - describe('.getProcedureNames()', () => - { - it('should contain all registered procedure', () => - { - const names = worker.getProcedureNames(); - - expect(names).toContain('first'); - expect(names).toContain('second'); - }); - }); - - describe('.hasProcedure(name)', () => - { - it('should find a procedure', () => - { - const hasFirstProcedure = worker.hasProcedure('first'); - const hasSecondProcedure = worker.hasProcedure('second'); - - expect(hasFirstProcedure).toBeTruthy(); - expect(hasSecondProcedure).toBeTruthy(); - }); - - it('should not find a procedure', () => - { - const hasNoProcedure = worker.hasProcedure('third'); - - expect(hasNoProcedure).toBeFalsy(); - }); - }); -}); diff --git a/packages/runtime/test/services/WorkerBalancer.spec.ts b/packages/runtime/test/services/WorkerBalancer.spec.ts deleted file mode 100644 index 3d485309..00000000 --- a/packages/runtime/test/services/WorkerBalancer.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ - -import { describe, expect, it } from 'vitest'; - -import NoWorkerAvailable from '../../src/errors/NoWorkerAvailable'; -import Request from '../../src/models/Request'; -import Version from '../../src/models/Version'; - -import { BALANCERS, WORKERS } from '../_fixtures/services/WorkerBalancer.fixture'; - -const balancer = BALANCERS.FILLED; -const emptyBalancer = BALANCERS.EMPTY; - -describe('services/WorkerBalancer', () => -{ - describe('.getNextWorker()', () => - { - it('should select workers round robin', async () => - { - const firstSelectedWorker = balancer.getNextWorker(); - const secondSelectedWorker = balancer.getNextWorker(); - const thirdSelectedWorker = balancer.getNextWorker(); - const fourthSelectedWorker = balancer.getNextWorker(); - - expect(firstSelectedWorker).toBe(WORKERS.FIRST); - expect(secondSelectedWorker).toBe(WORKERS.SECOND); - expect(thirdSelectedWorker).toBe(WORKERS.FIRST); - expect(fourthSelectedWorker).toBe(WORKERS.SECOND); - }); - }); - - describe('.run(name, version, parameters)', () => - { - it('should throw a worker not available error', async () => - { - const request = new Request('nonExisting', Version.DEFAULT, new Map(), new Map()); - const run = async () => emptyBalancer.run(request); - - expect(run).rejects.toEqual(new NoWorkerAvailable('nonExisting')); - }); - }); -}); diff --git a/packages/runtime/test/services/WorkerMonitor.spec.ts b/packages/runtime/test/services/WorkerMonitor.spec.ts deleted file mode 100644 index 566b7898..00000000 --- a/packages/runtime/test/services/WorkerMonitor.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ - -import { describe, expect, it } from 'vitest'; - -import { MONITORS, GATEWAY, WORKERS } from '../_fixtures/services/WorkerMonitor.fixture'; - -const monitor = MONITORS.HEALTH; - -describe('services/WorkerMonitor', () => -{ - describe('.monitor()', () => - { - it('should keep a worker and remove a worker', async () => - { - const beforeWorkers = GATEWAY.workers; - - expect(beforeWorkers.length).toBe(2); - expect(beforeWorkers[0]).toBe(WORKERS.GOOD); - expect(beforeWorkers[1]).toBe(WORKERS.BAD); - - monitor.start(); - await new Promise(resolve => setTimeout(resolve, 300)); - monitor.stop(); - - const afterWorkers = GATEWAY.workers; - - expect(afterWorkers.length).toBe(1); - expect(afterWorkers[0]).toBe(WORKERS.GOOD); - }); - }); -}); diff --git a/packages/services/src/gateway/LocalGateway.ts b/packages/services/src/gateway/LocalGateway.ts index 39d756c7..ec510bbe 100644 --- a/packages/services/src/gateway/LocalGateway.ts +++ b/packages/services/src/gateway/LocalGateway.ts @@ -56,7 +56,7 @@ export default class LocalGateway implements Gateway return new Map(); } - addWorker(worker: Worker, trustKey?: string): Promise + async addWorker(worker: Worker, trustKey?: string): Promise { if (this.#isInvalidTrustKey(trustKey)) { diff --git a/packages/services/src/gateway/WorkerBalancer.ts b/packages/services/src/gateway/WorkerBalancer.ts index f2013dfe..57fce3db 100644 --- a/packages/services/src/gateway/WorkerBalancer.ts +++ b/packages/services/src/gateway/WorkerBalancer.ts @@ -10,6 +10,8 @@ export default class WorkerBalancer #workers: Worker[] = []; #currentIndex = 0; + get workers() { return this.#workers; } + addWorker(worker: Worker): void { if (this.#workers.includes(worker)) @@ -47,7 +49,7 @@ export default class WorkerBalancer return this.#workers[this.#currentIndex++]; } - run(request: Request): Promise + async run(request: Request): Promise { const worker = this.getNextWorker(); diff --git a/packages/services/src/gateway/WorkerManager.ts b/packages/services/src/gateway/WorkerManager.ts index 4950e9cc..273c82a9 100644 --- a/packages/services/src/gateway/WorkerManager.ts +++ b/packages/services/src/gateway/WorkerManager.ts @@ -14,6 +14,8 @@ export default class WorkerManager implements Runner return [...this.#workers.values()]; } + get balancers() { return this.#balancers; } + getProcedureNames(): string[] { const procedureNames = this.workers.map(worker => worker.getProcedureNames()); @@ -77,7 +79,7 @@ export default class WorkerManager implements Runner return balancer; } - run(request: Request): Promise + async run(request: Request): Promise { const balancer = this.#getBalancer(request.fqn); diff --git a/packages/services/src/gateway/WorkerMonitor.ts b/packages/services/src/gateway/WorkerMonitor.ts index 228057df..ed5a1891 100644 --- a/packages/services/src/gateway/WorkerMonitor.ts +++ b/packages/services/src/gateway/WorkerMonitor.ts @@ -17,6 +17,8 @@ export default class WorkerMonitor this.#frequency = frequency; } + get workerManager() { return this.#workerManager; } + start(): void { this.#interval = setInterval(async () => this.#monitor(), this.#frequency); diff --git a/packages/services/src/repository/LocalRepository.ts b/packages/services/src/repository/LocalRepository.ts index 452eb923..977de25a 100644 --- a/packages/services/src/repository/LocalRepository.ts +++ b/packages/services/src/repository/LocalRepository.ts @@ -56,7 +56,7 @@ export default class LocalRepository implements Repository return new Map(); } - provide(filename: string): Promise + async provide(filename: string): Promise { if (this.#mustProvideIndex(filename)) { diff --git a/packages/services/test/gateway/LocalGateway.spec.ts b/packages/services/test/gateway/LocalGateway.spec.ts new file mode 100644 index 00000000..d53d2014 --- /dev/null +++ b/packages/services/test/gateway/LocalGateway.spec.ts @@ -0,0 +1,52 @@ + +import { describe, expect, it } from 'vitest'; + +import InvalidTrustKey from '../../src/gateway/errors/InvalidTrustKey'; + +import { LOCAL_GATEWAYS, REMOTE_WORKERS, VALUES } from './fixtures'; + +describe('gateway/LocalGateway', () => +{ + describe('.addWorker(worker, trustKey?)', () => + { + it('should add a worker without a trust key to a public gateway', () => + { + const gateway = LOCAL_GATEWAYS.PUBLIC; + const worker = REMOTE_WORKERS.EMPTY; + + const promise = gateway.addWorker(worker); + + expect(promise).resolves.toBeUndefined(); + }); + + it('should add a worker with a valid trust key to a protected gateway', () => + { + const gateway = LOCAL_GATEWAYS.PROTECTED; + const worker = REMOTE_WORKERS.EMPTY; + + const promise = gateway.addWorker(worker, VALUES.TRUST_KEY); + + expect(promise).resolves.toBeUndefined(); + }); + + it('should not add a worker with an invalid trust key to a protected gateway', () => + { + const gateway = LOCAL_GATEWAYS.PROTECTED; + const worker = REMOTE_WORKERS.EMPTY; + + const promise = gateway.addWorker(worker, 'INCORRECT_ACCESS_KEY'); + + expect(promise).rejects.toEqual(new InvalidTrustKey()); + }); + + it('should not add a worker with a missing trust key to a protected gateway', () => + { + const gateway = LOCAL_GATEWAYS.PROTECTED; + const worker = REMOTE_WORKERS.EMPTY; + + const promise = gateway.addWorker(worker); + + expect(promise).rejects.toEqual(new InvalidTrustKey()); + }); + }); +}); diff --git a/packages/services/test/gateway/WorkerBalancer.spec.ts b/packages/services/test/gateway/WorkerBalancer.spec.ts new file mode 100644 index 00000000..733405d6 --- /dev/null +++ b/packages/services/test/gateway/WorkerBalancer.spec.ts @@ -0,0 +1,42 @@ + +import { describe, expect, it } from 'vitest'; + +import { Request, RunModes, Version } from '@jitar/execution'; + +import NoWorkerAvailable from '../../src/gateway/errors/NoWorkerAvailable'; + +import { WORKER_BALANCERS, REMOTE_WORKERS } from './fixtures'; + +describe('services/WorkerBalancer', () => +{ + describe('.getNextWorker()', () => + { + it('should select workers round robin', async () => + { + const balancer = WORKER_BALANCERS.FILLED; + + const firstSelectedWorker = balancer.getNextWorker(); + const secondSelectedWorker = balancer.getNextWorker(); + const thirdSelectedWorker = balancer.getNextWorker(); + const fourthSelectedWorker = balancer.getNextWorker(); + + expect(firstSelectedWorker).toBe(REMOTE_WORKERS.FIRST); + expect(secondSelectedWorker).toBe(REMOTE_WORKERS.SECOND); + expect(thirdSelectedWorker).toBe(REMOTE_WORKERS.FIRST); + expect(fourthSelectedWorker).toBe(REMOTE_WORKERS.SECOND); + }); + }); + + describe('.run(request)', () => + { + it('should throw a worker not available error', async () => + { + const balancer = WORKER_BALANCERS.EMPTY; + + const request = new Request('nonExisting', Version.DEFAULT, new Map(), new Map(), RunModes.NORMAL); + const promise = balancer.run(request); + + expect(promise).rejects.toEqual(new NoWorkerAvailable('nonExisting')); + }); + }); +}); diff --git a/packages/services/test/gateway/WorkerManager.spec.ts b/packages/services/test/gateway/WorkerManager.spec.ts new file mode 100644 index 00000000..71011016 --- /dev/null +++ b/packages/services/test/gateway/WorkerManager.spec.ts @@ -0,0 +1,87 @@ + +import { describe, expect, it } from 'vitest'; + +import { Request, Response, RunModes, StatusCodes, Version, ProcedureNotFound } from '@jitar/execution'; + +import { WORKER_MANAGERS, REMOTE_WORKERS } from './fixtures'; + +describe('gateway/WorkerManager', () => +{ + describe('.getProcedureNames()', () => + { + it('should get the unique procedure names', () => + { + const manager = WORKER_MANAGERS.FILLED; + + const procedureNames = manager.getProcedureNames(); + expect(procedureNames).toEqual(['first', 'second']); + }); + }); + + describe('.hasProcedure()', () => + { + it('should confirm containing existing procedure names', () => + { + const manager = WORKER_MANAGERS.FILLED; + + const hasFirst = manager.hasProcedure('first'); + expect(hasFirst).toBeTruthy(); + + const hasSecond = manager.hasProcedure('second'); + expect(hasSecond).toBeTruthy(); + }); + + it('should deny containing non-existing procedure names', () => + { + const manager = WORKER_MANAGERS.FILLED; + + const hasThird = manager.hasProcedure('third'); + expect(hasThird).toBeFalsy(); + }); + }); + + describe('.addWorker(worker)', () => + { + // The workers are already added in the fixtures + // thus we only need to test the balancers. + + it('should create balancers for added workers', () => + { + const manager = WORKER_MANAGERS.FILLED; + + const balancers = manager.balancers; + expect(balancers.size).toBe(2); + + const firstBalancer = balancers.get('first'); + expect(firstBalancer).toBeDefined(); + expect(firstBalancer?.workers).toEqual([REMOTE_WORKERS.FIRST, REMOTE_WORKERS.SECOND]); + + const secondBalancer = balancers.get('second'); + expect(secondBalancer).toBeDefined(); + expect(secondBalancer?.workers).toEqual([REMOTE_WORKERS.SECOND]); + }); + }); + + describe('.run(request)', () => + { + it('should run an existing procedure', () => + { + const manager = WORKER_MANAGERS.FILLED; + const request = new Request('first', Version.DEFAULT, new Map(), new Map(), RunModes.NORMAL); + + const promise = manager.run(request); + + expect(promise).resolves.toEqual(new Response(StatusCodes.OK, 'test')); + }); + + it('should not run a non-existing procedure', () => + { + const manager = WORKER_MANAGERS.FILLED; + const request = new Request('nonExisting', Version.DEFAULT, new Map(), new Map(), RunModes.NORMAL); + + const promise = manager.run(request); + + expect(promise).rejects.toEqual(new ProcedureNotFound('nonExisting')); + }); + }); +}); diff --git a/packages/services/test/gateway/WorkerMonitor.spec.ts b/packages/services/test/gateway/WorkerMonitor.spec.ts new file mode 100644 index 00000000..ab9515b0 --- /dev/null +++ b/packages/services/test/gateway/WorkerMonitor.spec.ts @@ -0,0 +1,29 @@ + +import { describe, expect, it } from 'vitest'; + +import { WORKER_MONITORS, REMOTE_WORKERS } from './fixtures'; + +describe('gateway/WorkerMonitor', () => +{ + it('should remove unhealthy workers', async () => + { + const monitor = WORKER_MONITORS.EMPTY; + + const manager = monitor.workerManager; + manager.addWorker(REMOTE_WORKERS.HEALTHY); + manager.addWorker(REMOTE_WORKERS.UNHEALTHY); + + const beforeWorkers = manager.workers; + expect(beforeWorkers.length).toBe(2); + expect(beforeWorkers[0]).toBe(REMOTE_WORKERS.HEALTHY); + expect(beforeWorkers[1]).toBe(REMOTE_WORKERS.UNHEALTHY); + + monitor.start(); + await new Promise(resolve => setTimeout(resolve, 300)); + monitor.stop(); + + const afterWorkers = manager.workers; + expect(afterWorkers.length).toBe(1); + expect(afterWorkers[0]).toBe(REMOTE_WORKERS.HEALTHY); + }); +}); diff --git a/packages/services/test/gateway/fixtures/index.ts b/packages/services/test/gateway/fixtures/index.ts new file mode 100644 index 00000000..c1852c9d --- /dev/null +++ b/packages/services/test/gateway/fixtures/index.ts @@ -0,0 +1,8 @@ + +export * from './localGateways.fixture'; +export * from './remoteWorkers.fixture'; +export * from './values.fixture'; +export * from './remotes.fixture'; +export * from './workerBalancers.fixture'; +export * from './workerManagers.fixture'; +export * from './workerMonitors.fixture'; diff --git a/packages/services/test/gateway/fixtures/localGateways.fixture.ts b/packages/services/test/gateway/fixtures/localGateways.fixture.ts new file mode 100644 index 00000000..e171ab30 --- /dev/null +++ b/packages/services/test/gateway/fixtures/localGateways.fixture.ts @@ -0,0 +1,16 @@ + +import LocalGateway from '../../../src/gateway/LocalGateway'; + +import { VALUES } from './values.fixture'; + +const url = VALUES.URL; +const trustKey = VALUES.TRUST_KEY; + +const publicGateway = new LocalGateway({ url }); +const protectedGateway = new LocalGateway({ url, trustKey}); + +export const LOCAL_GATEWAYS = +{ + PUBLIC: publicGateway, + PROTECTED: protectedGateway +}; diff --git a/packages/services/test/gateway/fixtures/remoteWorkers.fixture.ts b/packages/services/test/gateway/fixtures/remoteWorkers.fixture.ts new file mode 100644 index 00000000..1c0d1826 --- /dev/null +++ b/packages/services/test/gateway/fixtures/remoteWorkers.fixture.ts @@ -0,0 +1,45 @@ + +import RemoteWorker from '../../../src/worker/RemoteWorker'; + +import { VALUES } from './values.fixture'; +import { REMOTES } from './remotes.fixture'; + +class HealthyWorker extends RemoteWorker +{ + async isHealthy(): Promise + { + return true; + } +} + +class UnhealthyWorker extends RemoteWorker +{ + async isHealthy(): Promise + { + return false; + } +} + +const url = VALUES.URL; +const remote = REMOTES.DUMMY; + +const noProcedureNames = new Set(); +const emptyWorker = new RemoteWorker({ url, procedureNames: noProcedureNames, remote }); + +const firstProcedureNames = new Set(['first']); +const firstWorker = new RemoteWorker({ url, procedureNames: firstProcedureNames, remote }); + +const secondProcedureNames = new Set(['first', 'second']); +const secondWorker = new RemoteWorker({ url, procedureNames: secondProcedureNames, remote }); + +const healthyWorker = new HealthyWorker({ url, procedureNames: noProcedureNames, remote }); +const unhealthyWorker = new UnhealthyWorker({ url, procedureNames: noProcedureNames, remote }); + +export const REMOTE_WORKERS = +{ + EMPTY: emptyWorker, + FIRST: firstWorker, + SECOND: secondWorker, + HEALTHY: healthyWorker, + UNHEALTHY: unhealthyWorker +}; diff --git a/packages/services/test/gateway/fixtures/remotes.fixture.ts b/packages/services/test/gateway/fixtures/remotes.fixture.ts new file mode 100644 index 00000000..c1055220 --- /dev/null +++ b/packages/services/test/gateway/fixtures/remotes.fixture.ts @@ -0,0 +1,51 @@ + +import { Request, Response, StatusCodes } from '@jitar/execution'; +import { File } from '@jitar/sourcing'; + +import Remote from '../../../src/Remote'; + +class DummyRemote implements Remote +{ + connect(): Promise + { + throw new Error('Method not implemented.'); + } + + disconnect(): Promise + { + throw new Error('Method not implemented.'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + provide(filename: string): Promise + { + throw new Error('Method not implemented.'); + } + + isHealthy(): Promise + { + throw new Error('Method not implemented.'); + } + + getHealth(): Promise> + { + throw new Error('Method not implemented.'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + addWorker(workerUrl: string, procedureNames: string[], trustKey?: string): Promise + { + throw new Error('Method not implemented.'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async run(request: Request): Promise + { + return new Response(StatusCodes.OK, 'test'); + } +} + +export const REMOTES = +{ + DUMMY: new DummyRemote() +}; diff --git a/packages/services/test/gateway/fixtures/values.fixture.ts b/packages/services/test/gateway/fixtures/values.fixture.ts new file mode 100644 index 00000000..d292da82 --- /dev/null +++ b/packages/services/test/gateway/fixtures/values.fixture.ts @@ -0,0 +1,6 @@ + +export const VALUES = +{ + URL: 'http://localhost:80', + TRUST_KEY: 'MY_TRUSTED_ACCESS_KEY' +}; diff --git a/packages/services/test/gateway/fixtures/workerBalancers.fixture.ts b/packages/services/test/gateway/fixtures/workerBalancers.fixture.ts new file mode 100644 index 00000000..50dfd8c4 --- /dev/null +++ b/packages/services/test/gateway/fixtures/workerBalancers.fixture.ts @@ -0,0 +1,16 @@ + +import WorkerBalancer from '../../../src/gateway/WorkerBalancer'; + +import { REMOTE_WORKERS } from './remoteWorkers.fixture'; + +const emptyBalancer = new WorkerBalancer(); + +const filledBalancer = new WorkerBalancer(); +filledBalancer.addWorker(REMOTE_WORKERS.FIRST); +filledBalancer.addWorker(REMOTE_WORKERS.SECOND); + +export const WORKER_BALANCERS = +{ + EMPTY: emptyBalancer, + FILLED: filledBalancer +}; diff --git a/packages/services/test/gateway/fixtures/workerManagers.fixture.ts b/packages/services/test/gateway/fixtures/workerManagers.fixture.ts new file mode 100644 index 00000000..f6a6043b --- /dev/null +++ b/packages/services/test/gateway/fixtures/workerManagers.fixture.ts @@ -0,0 +1,13 @@ + +import WorkerManager from '../../../src/gateway/WorkerManager'; + +import { REMOTE_WORKERS } from './remoteWorkers.fixture'; + +const filledManager = new WorkerManager(); +filledManager.addWorker(REMOTE_WORKERS.FIRST); +filledManager.addWorker(REMOTE_WORKERS.SECOND); + +export const WORKER_MANAGERS = +{ + FILLED: filledManager +}; diff --git a/packages/services/test/gateway/fixtures/workerMonitors.fixture.ts b/packages/services/test/gateway/fixtures/workerMonitors.fixture.ts new file mode 100644 index 00000000..2efc5661 --- /dev/null +++ b/packages/services/test/gateway/fixtures/workerMonitors.fixture.ts @@ -0,0 +1,11 @@ + +import WorkerManager from '../../../src/gateway/WorkerManager'; +import WorkerMonitor from '../../../src/gateway/WorkerMonitor'; + +const emptyManager = new WorkerManager(); +const emptyMonitor = new WorkerMonitor(emptyManager, 200); + +export const WORKER_MONITORS = +{ + EMPTY: emptyMonitor +}; diff --git a/packages/services/test/repository/LocalRepository.spec.ts b/packages/services/test/repository/LocalRepository.spec.ts new file mode 100644 index 00000000..1488133b --- /dev/null +++ b/packages/services/test/repository/LocalRepository.spec.ts @@ -0,0 +1,39 @@ + +import { describe, expect, it } from 'vitest'; + +import { FileNotFound } from '@jitar/sourcing'; + +import { LOCAL_REPOSITORIES, FILENAMES, FILES } from './fixtures'; + +describe('repository/LocalRepository', () => +{ + describe('.provide(filename)', () => + { + it('should provide a existing file', () => + { + const repository = LOCAL_REPOSITORIES.FILE; + + const promise = repository.provide(FILENAMES.PNG); + + expect(promise).resolves.toEqual(FILES.PNG); + }); + + it('should not provide a non-existing file', () => + { + const repository = LOCAL_REPOSITORIES.FILE; + + const promise = repository.provide(FILENAMES.TXT); + + expect(promise).rejects.toEqual(new FileNotFound(FILENAMES.TXT)); + }); + + it('should provide index file when file not found', () => + { + const repository = LOCAL_REPOSITORIES.WEB; + + const promise = repository.provide(FILENAMES.TXT); + + expect(promise).resolves.toEqual(FILES.HTML); + }); + }); +}); diff --git a/packages/services/test/repository/fixtures/filenames.fixture.ts b/packages/services/test/repository/fixtures/filenames.fixture.ts new file mode 100644 index 00000000..1a0adc78 --- /dev/null +++ b/packages/services/test/repository/fixtures/filenames.fixture.ts @@ -0,0 +1,7 @@ + +export const FILENAMES = +{ + HTML: 'index.html', + PNG: 'logo.png', + TXT: 'non-existing.txt', +}; diff --git a/packages/services/test/repository/fixtures/files.fixtures.ts b/packages/services/test/repository/fixtures/files.fixtures.ts new file mode 100644 index 00000000..36c37f3f --- /dev/null +++ b/packages/services/test/repository/fixtures/files.fixtures.ts @@ -0,0 +1,8 @@ + +import { File } from '@jitar/sourcing'; + +export const FILES = +{ + HTML: new File('index.html', 'text/html', Buffer.from('Hello, world!')), + PNG: new File('logo.png', 'image/png', Buffer.from('Fake image')) +}; diff --git a/packages/services/test/repository/fixtures/index.ts b/packages/services/test/repository/fixtures/index.ts new file mode 100644 index 00000000..cd51605e --- /dev/null +++ b/packages/services/test/repository/fixtures/index.ts @@ -0,0 +1,5 @@ + +export * from './filenames.fixture'; +export * from './files.fixtures'; +export * from './localRepositories.fixture'; +export * from './sourcingManager.fixture'; diff --git a/packages/services/test/repository/fixtures/localRepositories.fixture.ts b/packages/services/test/repository/fixtures/localRepositories.fixture.ts new file mode 100644 index 00000000..f32ddc02 --- /dev/null +++ b/packages/services/test/repository/fixtures/localRepositories.fixture.ts @@ -0,0 +1,18 @@ + +import LocalRepository from '../../../src/repository/LocalRepository'; + +import { sourcingManager } from './sourcingManager.fixture'; + +const url = 'http://localhost:80'; +const assets = new Set(['index.html', 'logo.png']); +const indexFilename = 'index.html'; +const serveIndexOnNotFound = true; + +const fileRepository = new LocalRepository({ url, assets, sourcingManager }) +const webRepository = new LocalRepository({ url, assets, sourcingManager, indexFilename, serveIndexOnNotFound }); + +export const LOCAL_REPOSITORIES = +{ + FILE: fileRepository, + WEB: webRepository +}; diff --git a/packages/services/test/repository/fixtures/sourcingManager.fixture.ts b/packages/services/test/repository/fixtures/sourcingManager.fixture.ts new file mode 100644 index 00000000..727e809f --- /dev/null +++ b/packages/services/test/repository/fixtures/sourcingManager.fixture.ts @@ -0,0 +1,73 @@ + +import { File, FileManager, FileNotFound, SourcingManager } from '@jitar/sourcing'; + +import { FILENAMES } from './filenames.fixture'; +import { FILES } from './files.fixtures'; + +class DummyFileManager implements FileManager +{ + getRootLocation(): string + { + throw new Error('Method not implemented.'); + } + + getAbsoluteLocation(filename: string): string + { + throw new Error('Method not implemented.'); + } + + getRelativeLocation(filename: string): string + { + throw new Error('Method not implemented.'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getType(filename: string): Promise + { + throw new Error('Method not implemented.'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getContent(filename: string): Promise + { + throw new Error('Method not implemented.'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + exists(filename: string): Promise + { + throw new Error('Method not implemented.'); + } + + async read(filename: string): Promise + { + switch (filename) + { + case FILENAMES.HTML: return FILES.HTML + case FILENAMES.PNG: return FILES.PNG + default: throw new FileNotFound(filename); + } + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + write(filename: string, content: string): Promise + { + throw new Error('Method not implemented.'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + delete(filename: string): Promise + { + throw new Error('Method not implemented.'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + filter(pattern: string): Promise + { + throw new Error('Method not implemented.'); + } +} + +const fileManager = new DummyFileManager(); + +export const sourcingManager = new SourcingManager(fileManager); diff --git a/packages/services/test/worker/LocalWorker.spec.ts b/packages/services/test/worker/LocalWorker.spec.ts new file mode 100644 index 00000000..3d76136d --- /dev/null +++ b/packages/services/test/worker/LocalWorker.spec.ts @@ -0,0 +1,76 @@ + +import { describe, expect, it } from 'vitest'; + +import { Request, Version, ProcedureNotFound, RunModes } from '@jitar/execution'; + +import RequestNotTrusted from '../../src/worker/errors/RequestNotTrusted'; + +import { LOCAL_WORKERS, VALUES } from './fixtures'; + +describe('worker/LocalWorker', () => +{ + describe('.run(request)', () => + { + it('should run a public procedure ', async () => + { + const worker = LOCAL_WORKERS.PUBLIC; + + const headers = new Map().set('x-jitar-trust-key', VALUES.TRUST_KEY); + const request = new Request('public', Version.DEFAULT, new Map(), headers, RunModes.NORMAL); + + const response = await worker.run(request); + + expect(response.result).toBe('public'); + }); + + it('should run a protected procedure with valid trust key', async () => + { + const worker = LOCAL_WORKERS.PROTECTED; + + const headers = new Map().set('x-jitar-trust-key', VALUES.TRUST_KEY); + const request = new Request('protected', Version.DEFAULT, new Map(), headers, RunModes.NORMAL); + + const response = await worker.run(request); + + expect(response.result).toBe('protected'); + }); + + it('should not run a non-existing procedure', async () => + { + const worker = LOCAL_WORKERS.PROTECTED; + + const request = new Request('nonExisting', Version.DEFAULT, new Map(), new Map(), RunModes.NORMAL); + + const promise = worker.run(request); + + expect(promise).rejects.toEqual(new ProcedureNotFound('nonExisting')); + }); + + it('should not run a protected procedure with invalid trust key', async () => + { + const worker = LOCAL_WORKERS.PROTECTED; + + const headers = new Map().set('x-jitar-trust-key', 'invalid'); + const request = new Request('protected', Version.DEFAULT, new Map(), headers, RunModes.NORMAL); + + const promise = worker.run(request); + + expect(promise).rejects.toEqual(new RequestNotTrusted()); + }); + + it('should not run a protected procedure without trust key', async () => + { + const worker = LOCAL_WORKERS.PROTECTED; + + const request = new Request('protected', Version.DEFAULT, new Map(), new Map(), RunModes.NORMAL); + + const promise = worker.run(request); + + expect(promise).rejects.toEqual(new RequestNotTrusted()); + }); + + // TODO: Add tests for remote execution + + // TODO: Add tests for (de)serialization + }); +}); diff --git a/packages/services/test/worker/fixtures/executionManager.fixture.ts b/packages/services/test/worker/fixtures/executionManager.fixture.ts new file mode 100644 index 00000000..ba256595 --- /dev/null +++ b/packages/services/test/worker/fixtures/executionManager.fixture.ts @@ -0,0 +1,8 @@ + +import { ExecutionManager } from '@jitar/execution'; + +import { SEGMENTS } from './segments.fixture'; + +export const executionManager = new ExecutionManager(); +executionManager.addSegment(SEGMENTS.FIRST); +executionManager.addSegment(SEGMENTS.SECOND); diff --git a/packages/services/test/worker/fixtures/index.ts b/packages/services/test/worker/fixtures/index.ts new file mode 100644 index 00000000..f6103a63 --- /dev/null +++ b/packages/services/test/worker/fixtures/index.ts @@ -0,0 +1,6 @@ + +export * from './executionManager.fixture'; +export * from './localWorkers.fixture'; +export * from './procedures.fixture'; +export * from './segments.fixture'; +export * from './values.fixture'; diff --git a/packages/services/test/worker/fixtures/localWorkers.fixture.ts b/packages/services/test/worker/fixtures/localWorkers.fixture.ts new file mode 100644 index 00000000..5445ec35 --- /dev/null +++ b/packages/services/test/worker/fixtures/localWorkers.fixture.ts @@ -0,0 +1,17 @@ + +import LocalWorker from '../../../src/worker/LocalWorker'; + +import { executionManager } from './executionManager.fixture'; +import { VALUES } from './values.fixture'; + +const url = VALUES.URL; +const trustKey = VALUES.TRUST_KEY; + +const publicWorker = new LocalWorker({ url, executionManager}); +const protectedWorker = new LocalWorker({ url, trustKey, executionManager}); + +export const LOCAL_WORKERS = +{ + PUBLIC: publicWorker, + PROTECTED: protectedWorker +}; diff --git a/packages/services/test/worker/fixtures/procedures.fixture.ts b/packages/services/test/worker/fixtures/procedures.fixture.ts new file mode 100644 index 00000000..72d7ee6b --- /dev/null +++ b/packages/services/test/worker/fixtures/procedures.fixture.ts @@ -0,0 +1,14 @@ + +import { Procedure, Implementation, Version, AccessLevels } from '@jitar/execution'; + +const publicProcedure = new Procedure('public'); +publicProcedure.addImplementation(new Implementation(Version.DEFAULT, AccessLevels.PUBLIC, [], () => 'public')); + +const protectedProcedure = new Procedure('protected'); +protectedProcedure.addImplementation(new Implementation(Version.DEFAULT, AccessLevels.PROTECTED, [], () => 'protected')); + +export const PROCEDURES = +{ + PUBLIC: publicProcedure, + PROTECTED: protectedProcedure +}; diff --git a/packages/services/test/worker/fixtures/segments.fixture.ts b/packages/services/test/worker/fixtures/segments.fixture.ts new file mode 100644 index 00000000..338ddca8 --- /dev/null +++ b/packages/services/test/worker/fixtures/segments.fixture.ts @@ -0,0 +1,16 @@ + +import { Segment } from '@jitar/execution'; + +import { PROCEDURES } from './procedures.fixture'; + +const firstSegment = new Segment('first' ); +firstSegment.addProcedure(PROCEDURES.PUBLIC); + +const secondSegment = new Segment('second'); +secondSegment.addProcedure(PROCEDURES.PROTECTED); + +export const SEGMENTS = +{ + FIRST: firstSegment, + SECOND: secondSegment +}; diff --git a/packages/services/test/worker/fixtures/values.fixture.ts b/packages/services/test/worker/fixtures/values.fixture.ts new file mode 100644 index 00000000..d292da82 --- /dev/null +++ b/packages/services/test/worker/fixtures/values.fixture.ts @@ -0,0 +1,6 @@ + +export const VALUES = +{ + URL: 'http://localhost:80', + TRUST_KEY: 'MY_TRUSTED_ACCESS_KEY' +};