diff --git a/package.json b/package.json index 0339efa..97766f0 100644 --- a/package.json +++ b/package.json @@ -65,9 +65,10 @@ "collectCoverageFrom": [ "src/**/*.ts", "!src/infra/**", - "!src/api/**", "!src/main.ts", - "!src/app.module.ts" + "!src/**/*.module.ts", + "!src/**/*.controller.ts", + "!src/**/*.dto.ts" ], "coverageReporters": [ "json", diff --git a/sonar-project.properties b/sonar-project.properties index 44d0b9d..50bffd5 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -19,7 +19,7 @@ sonar.tests=test sonar.language=ts # Configuração do caminho de exclusão da cobertura (excluindo a pasta 'infra') -sonar.coverage.exclusions=**/infra/**/*, **/api/**/*, **/src/main.ts +sonar.coverage.exclusions=**/infra/**/*, **/src/main.ts, **/*.module.ts , **/*.controller.ts , **/*.dto.ts # Diretório onde os relatórios de cobertura são gerados sonar.javascript.lcov.reportPaths=coverage/lcov.info diff --git a/src/app.module.ts b/src/app.module.ts index 7a42757..708cab4 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,12 +1,13 @@ import { Module } from '@nestjs/common'; -import { AppController } from './api/app.controller'; + import { ConfigModule } from '@nestjs/config'; import { LOGGER } from './infra/logger/logger'; import { ConsoleLogger } from './infra/logger/api-logger'; +import { HealthScoreModule } from './modules/health-score/health-score.module'; @Module({ - imports: [ConfigModule.forRoot()], - controllers: [AppController], + imports: [ConfigModule.forRoot(), HealthScoreModule], + controllers: [], providers: [ { provide: LOGGER, diff --git a/src/application/code-quality.service.ts b/src/application/code-quality.service.ts index 8be4b06..e62ac50 100644 --- a/src/application/code-quality.service.ts +++ b/src/application/code-quality.service.ts @@ -3,11 +3,19 @@ import { CompanyMeasureUsecase, } from './usecases/company-measure'; -import { QualityProvider } from '../domain/quality-provider'; +import { QualityProvider } from './quality-provider'; +import { TechnicalDebtProvider } from './technical-debt-provider'; +import { QualityMeasureResultDto } from '@/modules/health-score/quality-measure-result.dto'; export class CodeQualityService { - constructor(private readonly qualityProvider: QualityProvider) {} - async generateQualityMeasure(keys?: string, keyName?: string): Promise { + constructor( + private readonly qualityProvider: QualityProvider, + private readonly technicalDebtProvider: TechnicalDebtProvider, + ) { } + async generateQualityMeasure( + keys?: string, + keyName?: string, + ): Promise { const projectKeys = await this.getProjectKeysByKeyName(keys, keyName); const companyMeasureInput: CompanyMeasureInput = { @@ -17,10 +25,21 @@ export class CodeQualityService { const companyData = new CompanyMeasureUsecase( companyMeasureInput, this.qualityProvider, + this.technicalDebtProvider, ); + + const healthScore = await companyData.execute(); + return { - value: await companyData.execute(), - projects: projectKeys, + value: healthScore.value, + technicalDebt: healthScore.technicalDebt, + items: healthScore.items.map((item) => { + return { + project: item.project, + technicalDebt: item.technicalDebt, + value: item.value, + }; + }) }; } diff --git a/src/domain/quality-provider.ts b/src/application/quality-provider.ts similarity index 70% rename from src/domain/quality-provider.ts rename to src/application/quality-provider.ts index ea9bcf4..78dd186 100644 --- a/src/domain/quality-provider.ts +++ b/src/application/quality-provider.ts @@ -1,4 +1,4 @@ -import { QualityMeasures } from './measures/QualityMeasures'; +import { QualityMeasures } from '../domain/health-score/measures/QualityMeasures'; export interface QualityProvider { getMetricsDataFromProjectKey(projectKey: string): Promise; diff --git a/src/application/technical-debt-provider.ts b/src/application/technical-debt-provider.ts new file mode 100644 index 0000000..6fd41e7 --- /dev/null +++ b/src/application/technical-debt-provider.ts @@ -0,0 +1,5 @@ +import { TechnicalDebt } from '@/domain/technical-debt/TechnicalDebt'; + +export interface TechnicalDebtProvider { + getTechnicalDebtFromProject(project: string): Promise; +} diff --git a/src/application/usecases/company-measure.ts b/src/application/usecases/company-measure.ts index 97c70e2..fd30f99 100644 --- a/src/application/usecases/company-measure.ts +++ b/src/application/usecases/company-measure.ts @@ -1,18 +1,23 @@ -import { CompanyHealthCalculator } from '@/domain/CompanyHealthScore'; -import { Project } from '../../domain/Project'; -import { QualityProvider } from 'src/domain/quality-provider'; +import { Company } from '@/domain/Company'; +import { QualityProvider } from '../quality-provider'; +import { TechnicalDebtProvider } from '../technical-debt-provider'; +import { Project } from '@/domain/Project'; +import { HealthScore } from '@/domain/HealthScore'; export class CompanyMeasureUsecase { constructor( private readonly input: CompanyMeasureInput, private readonly qualityProviderService: QualityProvider, + private readonly tecnicalDebtService: TechnicalDebtProvider, ) {} - async execute(): Promise { + async execute(): Promise { const { projectKeys } = this.input; const projects = await this.getProjects(projectKeys); - const companyHealthCalculator = new CompanyHealthCalculator(projects); - return companyHealthCalculator.calculateHealthScore(); + const company = new Company(projects); + const healthScore = company.calculateHealthScore(); + + return healthScore; } private async getProjects(projectKeys: string[]): Promise { @@ -23,7 +28,11 @@ export class CompanyMeasureUsecase { projectKey, ); if (qualityMeasures) { - return new Project(qualityMeasures); + const technicalDebt = + await this.tecnicalDebtService.getTechnicalDebtFromProject( + projectKey, + ); + return new Project(projectKey, qualityMeasures, technicalDebt); } }), ); diff --git a/src/domain/Company.ts b/src/domain/Company.ts new file mode 100644 index 0000000..f426de1 --- /dev/null +++ b/src/domain/Company.ts @@ -0,0 +1,47 @@ +import { HealthScore, HealthScoreItem } from './HealthScore'; +import { Project } from './Project'; +import { NoProjectError } from './health-score/exceptions/InvalidProjectError'; + +export class Company { + private readonly projects: Project[]; + + constructor(projects: Project[]) { + this.projects = projects; + if (this.projects.length === 0) { + throw new NoProjectError(); + } + } + + public calculateHealthScore(): HealthScore { + const items = this.calculateItems(); + + const acummulatedScore = items.reduce((acc, item) => { + return acc + item.value + }, 0); + + const healthScoreRounded = + Math.round((acummulatedScore / this.projects.length) * 100) / 100; + + const acummulatedDebt = items.reduce((acc, item) => { + return acc + item.technicalDebt; + }, 0); + + return { + value: healthScoreRounded, + technicalDebt: acummulatedDebt, + items, + } + } + + private calculateItems(): HealthScoreItem[] { + return this.projects.map((project) => { + const technicalDebt = project.calculateTechnicalDebt(); + const value = project.calculateHealthScore(); + return { + project: project.name, + technicalDebt, + value, + }; + }); + } +} diff --git a/src/domain/CompanyHealthScore.ts b/src/domain/CompanyHealthScore.ts deleted file mode 100644 index e7096f6..0000000 --- a/src/domain/CompanyHealthScore.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Project } from './Project'; -import { NoProjectError } from './exceptions/InvalidProjectError'; - -export class CompanyHealthCalculator { - private readonly projects: Project[]; - - constructor(projects: Project[]) { - this.projects = projects; - if (this.projects.length === 0) { - throw new NoProjectError(); - } - } - - public calculateHealthScore(): number { - const acummulatedScore = this.projects.reduce((acc, project) => { - return acc + project.calculateHealthScore(); - }, 0); - const healthScoreRounded = - Math.round((acummulatedScore / this.projects.length) * 100) / 100; - - return healthScoreRounded; - } -} diff --git a/src/domain/HealthScore.ts b/src/domain/HealthScore.ts new file mode 100644 index 0000000..4e2a25a --- /dev/null +++ b/src/domain/HealthScore.ts @@ -0,0 +1,11 @@ +export class HealthScore { + value: number; + technicalDebt: number; + items: HealthScoreItem[]; +} + +export class HealthScoreItem { + project: string; + technicalDebt: number; + value: number; +} \ No newline at end of file diff --git a/src/domain/Project.ts b/src/domain/Project.ts index f4fc278..697b7df 100644 --- a/src/domain/Project.ts +++ b/src/domain/Project.ts @@ -1,11 +1,20 @@ -import { QualityMeasures } from './measures/QualityMeasures'; +import { QualityMeasures } from './health-score/measures/QualityMeasures'; +import { TechnicalDebt } from './technical-debt/TechnicalDebt'; export class Project { - constructor(private readonly qualityMeasures: QualityMeasures[]) {} + constructor( + public readonly name: string, + private readonly qualityMeasures: QualityMeasures[], + private readonly technicalDebt: TechnicalDebt, + ) { } calculateHealthScore(): number { return this.qualityMeasures.reduce((acc, qualityMeasure) => { return acc + qualityMeasure.getQualityRatio(); }, 0); } + + calculateTechnicalDebt(): number { + return this.technicalDebt.calculateTechnicalDebt(); + } } diff --git a/src/domain/exceptions/InvalidProjectError.ts b/src/domain/health-score/exceptions/InvalidProjectError.ts similarity index 100% rename from src/domain/exceptions/InvalidProjectError.ts rename to src/domain/health-score/exceptions/InvalidProjectError.ts diff --git a/src/domain/measures/CoverageQualityMeasure.ts b/src/domain/health-score/measures/CoverageQualityMeasure.ts similarity index 100% rename from src/domain/measures/CoverageQualityMeasure.ts rename to src/domain/health-score/measures/CoverageQualityMeasure.ts diff --git a/src/domain/measures/DuplicatedLinesMeasure.ts b/src/domain/health-score/measures/DuplicatedLinesMeasure.ts similarity index 100% rename from src/domain/measures/DuplicatedLinesMeasure.ts rename to src/domain/health-score/measures/DuplicatedLinesMeasure.ts diff --git a/src/domain/measures/ManutenabilityMeasure.ts b/src/domain/health-score/measures/ManutenabilityMeasure.ts similarity index 100% rename from src/domain/measures/ManutenabilityMeasure.ts rename to src/domain/health-score/measures/ManutenabilityMeasure.ts diff --git a/src/domain/measures/QualityMeasures.ts b/src/domain/health-score/measures/QualityMeasures.ts similarity index 91% rename from src/domain/measures/QualityMeasures.ts rename to src/domain/health-score/measures/QualityMeasures.ts index 268318f..c40d101 100644 --- a/src/domain/measures/QualityMeasures.ts +++ b/src/domain/health-score/measures/QualityMeasures.ts @@ -3,6 +3,8 @@ export interface QualityMeasures { getQualityRatio(): number; } +export const TECHICAL_DEBT = 'sqale_index'; + export enum QualityMeasuresTypes { RELIABILITY_RATING = 'reliability_rating', MANUTENABILITY_RATING = 'sqale_rating', diff --git a/src/domain/measures/ReliabilityMeasure.ts b/src/domain/health-score/measures/ReliabilityMeasure.ts similarity index 100% rename from src/domain/measures/ReliabilityMeasure.ts rename to src/domain/health-score/measures/ReliabilityMeasure.ts diff --git a/src/domain/measures/SecurityRatingMeasure.ts b/src/domain/health-score/measures/SecurityRatingMeasure.ts similarity index 100% rename from src/domain/measures/SecurityRatingMeasure.ts rename to src/domain/health-score/measures/SecurityRatingMeasure.ts diff --git a/src/domain/measures/SecurityReviewMeasure.ts b/src/domain/health-score/measures/SecurityReviewMeasure.ts similarity index 100% rename from src/domain/measures/SecurityReviewMeasure.ts rename to src/domain/health-score/measures/SecurityReviewMeasure.ts diff --git a/src/domain/technical-debt/TechnicalDebt.ts b/src/domain/technical-debt/TechnicalDebt.ts new file mode 100644 index 0000000..4da8674 --- /dev/null +++ b/src/domain/technical-debt/TechnicalDebt.ts @@ -0,0 +1,9 @@ +export class TechnicalDebt { + constructor(private readonly value: number) {} + + calculateTechnicalDebt(): number { + const hour = 60; + const technicalDebtInHours = this.value / hour; + return technicalDebtInHours; + } +} diff --git a/src/infra/sonar/sonar-http-client.ts b/src/infra/sonar/sonar-http-client.ts index 31ebe3f..ba107e2 100644 --- a/src/infra/sonar/sonar-http-client.ts +++ b/src/infra/sonar/sonar-http-client.ts @@ -1,10 +1,31 @@ import { HttpClient } from '../http/http-client'; -import { QualityMeasures } from '../../domain/measures/QualityMeasures'; -import { SonarAdapterMetrics } from './sonar-metrics-adapter'; -import { QualityProvider } from '@/domain/quality-provider'; +import { + QualityMeasures, + TECHICAL_DEBT, +} from '../../domain/health-score/measures/QualityMeasures'; -export class SonarHttpClient implements QualityProvider { +import { QualityProvider } from '@/application/quality-provider'; +import { SonarMetricsAdapter } from './sonar-metrics-adapter'; +import { TechnicalDebtProvider } from '@/application/technical-debt-provider'; +import { TechnicalDebt } from '@/domain/technical-debt/TechnicalDebt'; +import { SonarTechicalDebtAdapter } from './sonar-techical-debt-adapter'; + +export class SonarHttpClient implements QualityProvider, TechnicalDebtProvider { constructor(private readonly httpClient: HttpClient) {} + getTechnicalDebtFromProject(project: string): Promise { + const request = this.httpClient.get('/api/measures/component', { + params: { + component: project, + metricKeys: TECHICAL_DEBT, + }, + }); + + return request.then((data: SonarMetricResponse) => { + if (data.component.measures.length) { + return new SonarTechicalDebtAdapter(data).execute(); + } + }); + } getProjectsFromProjectKeyName(projectKeyName: string): Promise { const request = this.httpClient.get('/api/projects/search', { params: { @@ -29,7 +50,7 @@ export class SonarHttpClient implements QualityProvider { return request.then((data: SonarMetricResponse) => { if (data.component.measures.length) { - return new SonarAdapterMetrics(data).execute(); + return new SonarMetricsAdapter(data).execute(); } }); } diff --git a/src/infra/sonar/sonar-metrics-adapter.ts b/src/infra/sonar/sonar-metrics-adapter.ts index cffc280..a3b493e 100644 --- a/src/infra/sonar/sonar-metrics-adapter.ts +++ b/src/infra/sonar/sonar-metrics-adapter.ts @@ -1,16 +1,16 @@ import { QualityMeasures, QualityMeasuresTypes, -} from '../../domain/measures/QualityMeasures'; +} from '../../domain/health-score/measures/QualityMeasures'; import { SonarMetricResponse } from './sonar-http-client'; -import { ReliabilityMeasure } from '../../domain/measures/ReliabilityMeasure'; -import { CoverageQualityMeasure } from '../../domain/measures/CoverageQualityMeasure'; -import { ManutenabilityMeasure } from '../../domain/measures/ManutenabilityMeasure'; -import { SecurityRatingMeasure } from '../../domain/measures/SecurityRatingMeasure'; -import { DuplicatedLinesMeasure } from '../../domain/measures/DuplicatedLinesMeasure'; -import { SecurityReviewMeasure } from '../../domain/measures/SecurityReviewMeasure'; +import { ReliabilityMeasure } from '../../domain/health-score/measures/ReliabilityMeasure'; +import { CoverageQualityMeasure } from '../../domain/health-score/measures/CoverageQualityMeasure'; +import { ManutenabilityMeasure } from '../../domain/health-score/measures/ManutenabilityMeasure'; +import { SecurityRatingMeasure } from '../../domain/health-score/measures/SecurityRatingMeasure'; +import { DuplicatedLinesMeasure } from '../../domain/health-score/measures/DuplicatedLinesMeasure'; +import { SecurityReviewMeasure } from '../../domain/health-score/measures/SecurityReviewMeasure'; -export class SonarAdapterMetrics { +export class SonarMetricsAdapter { constructor(private readonly sonarResponse: SonarMetricResponse) {} execute(): QualityMeasures[] { const healthPanelMetrics = Object.values(QualityMeasuresTypes); diff --git a/src/infra/sonar/sonar-techical-debt-adapter.ts b/src/infra/sonar/sonar-techical-debt-adapter.ts new file mode 100644 index 0000000..c2db616 --- /dev/null +++ b/src/infra/sonar/sonar-techical-debt-adapter.ts @@ -0,0 +1,17 @@ +import { TECHICAL_DEBT } from '../../domain/health-score/measures/QualityMeasures'; +import { SonarMetricResponse } from './sonar-http-client'; + +import { TechnicalDebt } from '@/domain/technical-debt/TechnicalDebt'; + +export class SonarTechicalDebtAdapter { + constructor(private readonly sonarResponse: SonarMetricResponse) {} + execute(): TechnicalDebt { + const sonarMeasures = this.sonarResponse.component.measures; + + const technicalDebtValue = sonarMeasures.find( + (sonarMeasure) => sonarMeasure.metric === TECHICAL_DEBT, + ); + + return new TechnicalDebt(Number(technicalDebtValue.value)); + } +} diff --git a/src/main.ts b/src/main.ts index 3c6815b..fcbe95b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,9 +1,6 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; -import cluster from 'cluster'; -import os from 'os'; - import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; const port = process.env.PORT || 3000; @@ -19,23 +16,4 @@ async function bootstrap() { await app.listen(port); } -export class AppClusterService { - static clusterize(callback: Function): void { - const numCPUs = os.cpus().length; - - if (cluster.isPrimary) { - console.log(`Master server started on ${process.pid}`); - for (let i = 0; i < numCPUs; i++) { - cluster.fork(); - } - cluster.on('exit', (worker) => { - console.log(`Worker ${worker.process.pid} died. Restarting`); - cluster.fork(); - }); - } else { - console.log(`Cluster server started on ${process.pid}`); - callback(); - } - } -} -AppClusterService.clusterize(bootstrap); +bootstrap(); diff --git a/src/api/app.controller.ts b/src/modules/health-score/health-score.controller.ts similarity index 70% rename from src/api/app.controller.ts rename to src/modules/health-score/health-score.controller.ts index 66d9865..c03b86f 100644 --- a/src/api/app.controller.ts +++ b/src/modules/health-score/health-score.controller.ts @@ -1,17 +1,18 @@ import { Controller, Get, Inject, Query, ValidationPipe } from '@nestjs/common'; -import { CodeQualityService } from '../application/code-quality.service'; +import { CodeQualityService } from '../../application/code-quality.service'; import { QualityMeasureDto } from './quality-measure.dto'; import { ApiTags } from '@nestjs/swagger'; -import { AxiosAdapter } from '../infra/http/axios-adapter'; -import { SonarHttpClient } from '../infra/sonar/sonar-http-client'; +import { AxiosAdapter } from '../../infra/http/axios-adapter'; +import { SonarHttpClient } from '../../infra/sonar/sonar-http-client'; import { LOGGER, ILogger } from '@/infra/logger/logger'; +import { QualityMeasureResultDto } from './quality-measure-result.dto'; @Controller() -export class AppController { +export class HealthScoreController { constructor( @Inject(LOGGER) private readonly logger: ILogger, - ) {} + ) { } @Get() @ApiTags('Health Sync - Code Quality') generateQualityMeasure( @@ -23,7 +24,7 @@ export class AppController { }), ) query: QualityMeasureDto, - ): Promise { + ): Promise { const sonarUrl = query.sonarUrl || process.env.SONAR_URL; this.logger.log(`Sonar URL: ${sonarUrl}`); @@ -31,7 +32,10 @@ export class AppController { const httpAdapter = new AxiosAdapter(sonarUrl, sonarUserToken); const sonarHttpClient = new SonarHttpClient(httpAdapter); - const codeQualityService = new CodeQualityService(sonarHttpClient); + const codeQualityService = new CodeQualityService( + sonarHttpClient, + sonarHttpClient, + ); return codeQualityService.generateQualityMeasure( query.projectKeys, diff --git a/src/modules/health-score/health-score.module.ts b/src/modules/health-score/health-score.module.ts new file mode 100644 index 0000000..f32acbf --- /dev/null +++ b/src/modules/health-score/health-score.module.ts @@ -0,0 +1,15 @@ +import { ConsoleLogger, Module } from '@nestjs/common'; +import { HealthScoreController } from './health-score.controller'; +import { LOGGER } from '@/infra/logger/logger'; + +@Module({ + imports: [], + controllers: [HealthScoreController], + providers: [ + { + provide: LOGGER, + useClass: ConsoleLogger, + }, + ], +}) +export class HealthScoreModule {} diff --git a/src/modules/health-score/quality-measure-result.dto.ts b/src/modules/health-score/quality-measure-result.dto.ts new file mode 100644 index 0000000..d36ec43 --- /dev/null +++ b/src/modules/health-score/quality-measure-result.dto.ts @@ -0,0 +1,11 @@ +export class QualityMeasureResultDto { + value: number; + technicalDebt: number; + items: QualityMeasureResultItemDto[]; +} + +export class QualityMeasureResultItemDto { + project: string; + technicalDebt: number; + value: number; +} \ No newline at end of file diff --git a/src/api/quality-measure.dto.ts b/src/modules/health-score/quality-measure.dto.ts similarity index 100% rename from src/api/quality-measure.dto.ts rename to src/modules/health-score/quality-measure.dto.ts diff --git a/test/CodeQualityUseCase.spec.ts b/test/CodeQualityUseCase.spec.ts index 3c9d700..b3b179b 100644 --- a/test/CodeQualityUseCase.spec.ts +++ b/test/CodeQualityUseCase.spec.ts @@ -1,6 +1,8 @@ import { CodeQualityService } from '@/application/code-quality.service'; -import { CoverageQualityMeasure } from '@/domain/measures/CoverageQualityMeasure'; -import { QualityProvider } from '@/domain/quality-provider'; +import { QualityProvider } from '@/application/quality-provider'; +import { TechnicalDebtProvider } from '@/application/technical-debt-provider'; +import { CoverageQualityMeasure } from '@/domain/health-score/measures/CoverageQualityMeasure'; +import { TechnicalDebt } from '@/domain/technical-debt/TechnicalDebt'; describe('CodeQualityService', () => { test('Should generate value with project name', async () => { @@ -13,7 +15,19 @@ describe('CodeQualityService', () => { return Promise.resolve(['test']); }, }; - const codeQualityService = new CodeQualityService(qualityProvider); + + const tehcDebtProvider: TechnicalDebtProvider = { + getTechnicalDebtFromProject: function ( + project: string, + ): Promise { + return Promise.resolve(new TechnicalDebt(60)); + }, + }; + + const codeQualityService = new CodeQualityService( + qualityProvider, + tehcDebtProvider, + ); const qualityMeasures = await codeQualityService.generateQualityMeasure( 'test', @@ -21,6 +35,7 @@ describe('CodeQualityService', () => { expect(qualityMeasures).toEqual({ value: 16.67, + technicalDebt: 1, projects: ['test'], }); }); @@ -35,7 +50,19 @@ describe('CodeQualityService', () => { return Promise.resolve(['test']); }, }; - const codeQualityService = new CodeQualityService(qualityProvider); + + const tehcDebtProvider: TechnicalDebtProvider = { + getTechnicalDebtFromProject: function ( + project: string, + ): Promise { + return Promise.resolve(new TechnicalDebt(60)); + }, + }; + + const codeQualityService = new CodeQualityService( + qualityProvider, + tehcDebtProvider, + ); const qualityMeasures = await codeQualityService.generateQualityMeasure( '', @@ -44,6 +71,7 @@ describe('CodeQualityService', () => { expect(qualityMeasures).toEqual({ value: 16.67, + technicalDebt: 1, projects: ['test'], }); }); @@ -58,7 +86,18 @@ describe('CodeQualityService', () => { return Promise.resolve(['test']); }, }; - const codeQualityService = new CodeQualityService(qualityProvider); + + const tehcDebtProvider: TechnicalDebtProvider = { + getTechnicalDebtFromProject: function ( + project: string, + ): Promise { + return Promise.resolve(new TechnicalDebt(120)); + }, + }; + const codeQualityService = new CodeQualityService( + qualityProvider, + tehcDebtProvider, + ); const qualityMeasures = await codeQualityService.generateQualityMeasure( '', @@ -67,6 +106,7 @@ describe('CodeQualityService', () => { expect(qualityMeasures).toEqual({ value: 0, + technicalDebt: 2, projects: ['test'], }); }); @@ -81,7 +121,19 @@ describe('CodeQualityService', () => { return Promise.resolve(['test']); }, }; - const codeQualityService = new CodeQualityService(qualityProvider); + + const tehcDebtProvider: TechnicalDebtProvider = { + getTechnicalDebtFromProject: function ( + project: string, + ): Promise { + return Promise.resolve(new TechnicalDebt(60)); + }, + }; + + const codeQualityService = new CodeQualityService( + qualityProvider, + tehcDebtProvider, + ); await expect(() => { return codeQualityService.generateQualityMeasure('', ''); diff --git a/test/CompanyHealthScore.spec.ts b/test/CompanyHealthScore.spec.ts index 3491ed6..143d795 100644 --- a/test/CompanyHealthScore.spec.ts +++ b/test/CompanyHealthScore.spec.ts @@ -1,20 +1,19 @@ -import { CompanyHealthCalculator } from '@/domain/CompanyHealthScore'; +import { Company } from '@/domain/Company'; import { Project } from '@/domain/Project'; -import { NoProjectError } from '@/domain/exceptions/InvalidProjectError'; -import { CoverageQualityMeasure } from '@/domain/measures/CoverageQualityMeasure'; -import { DuplicatedLinesMeasure } from '@/domain/measures/DuplicatedLinesMeasure'; -import { ManutenabilityMeasure } from '@/domain/measures/ManutenabilityMeasure'; -import { QualityMeasures } from '@/domain/measures/QualityMeasures'; -import { ReliabilityMeasure } from '@/domain/measures/ReliabilityMeasure'; -import { SecurityRatingMeasure } from '@/domain/measures/SecurityRatingMeasure'; -import { SecurityReviewMeasure } from '@/domain/measures/SecurityReviewMeasure'; +import { NoProjectError } from '@/domain/health-score/exceptions/InvalidProjectError'; +import { CoverageQualityMeasure } from '@/domain/health-score/measures/CoverageQualityMeasure'; +import { DuplicatedLinesMeasure } from '@/domain/health-score/measures/DuplicatedLinesMeasure'; +import { ManutenabilityMeasure } from '@/domain/health-score/measures/ManutenabilityMeasure'; +import { QualityMeasures } from '@/domain/health-score/measures/QualityMeasures'; +import { ReliabilityMeasure } from '@/domain/health-score/measures/ReliabilityMeasure'; +import { SecurityRatingMeasure } from '@/domain/health-score/measures/SecurityRatingMeasure'; +import { SecurityReviewMeasure } from '@/domain/health-score/measures/SecurityReviewMeasure'; +import { TechnicalDebt } from '@/domain/technical-debt/TechnicalDebt'; describe('CompanyHealthCalculator', () => { describe('constructor', () => { it('should throw an error if no projects are provided', () => { - expect(() => new CompanyHealthCalculator([])).toThrowError( - NoProjectError, - ); + expect(() => new Company([])).toThrowError(NoProjectError); }); }); @@ -28,8 +27,9 @@ describe('CompanyHealthCalculator', () => { new SecurityReviewMeasure(1), new DuplicatedLinesMeasure(1), ]; - const projects = [new Project(qualityMeasures)]; - const companyHealthCalculator = new CompanyHealthCalculator(projects); + const technicalDebt = new TechnicalDebt(60); + const projects = [new Project(qualityMeasures, technicalDebt)]; + const companyHealthCalculator = new Company(projects); expect(companyHealthCalculator.calculateHealthScore()).toEqual(100); }); @@ -42,8 +42,9 @@ describe('CompanyHealthCalculator', () => { new SecurityReviewMeasure(5), new DuplicatedLinesMeasure(11), ]; - const projects = [new Project(qualityMeasures)]; - const companyHealthCalculator = new CompanyHealthCalculator(projects); + const technicalDebt = new TechnicalDebt(60); + const projects = [new Project(qualityMeasures, technicalDebt)]; + const companyHealthCalculator = new Company(projects); expect(companyHealthCalculator.calculateHealthScore()).toEqual(0); }); @@ -56,8 +57,9 @@ describe('CompanyHealthCalculator', () => { new SecurityReviewMeasure(5), new DuplicatedLinesMeasure(11), ]; - const projects = [new Project(qualityMeasures)]; - const companyHealthCalculator = new CompanyHealthCalculator(projects); + const technicalDebt = new TechnicalDebt(60); + const projects = [new Project(qualityMeasures, technicalDebt)]; + const companyHealthCalculator = new Company(projects); expect(companyHealthCalculator.calculateHealthScore()).toEqual(16.67); }); }); diff --git a/test/CoverageQualityMeasure.spec.ts b/test/CoverageQualityMeasure.spec.ts index d5e693d..effe728 100644 --- a/test/CoverageQualityMeasure.spec.ts +++ b/test/CoverageQualityMeasure.spec.ts @@ -1,5 +1,5 @@ -import { CoverageQualityMeasure } from '../src/domain/measures/CoverageQualityMeasure'; -import { QualityMeasuresValues } from '../src/domain/measures/QualityMeasures'; +import { CoverageQualityMeasure } from '../src/domain/health-score/measures/CoverageQualityMeasure'; +import { QualityMeasuresValues } from '../src/domain/health-score/measures/QualityMeasures'; describe('CoverageQualityMeasure', () => { it('should return A quality ratio for coverage value >= 80', () => { diff --git a/test/DuplicatedLinesMeasure.spec.ts b/test/DuplicatedLinesMeasure.spec.ts index a071a5e..ea23b6e 100644 --- a/test/DuplicatedLinesMeasure.spec.ts +++ b/test/DuplicatedLinesMeasure.spec.ts @@ -1,5 +1,5 @@ -import { DuplicatedLinesMeasure } from '@/domain/measures/DuplicatedLinesMeasure'; -import { QualityMeasuresValues } from '@/domain/measures/QualityMeasures'; +import { DuplicatedLinesMeasure } from '@/domain/health-score/measures/DuplicatedLinesMeasure'; +import { QualityMeasuresValues } from '@/domain/health-score/measures/QualityMeasures'; describe('DuplicatedLinesMeasure', () => { describe('getQualityRatio', () => { diff --git a/test/ManutenabilityMeasure.spec.ts b/test/ManutenabilityMeasure.spec.ts index fa03977..b9301c4 100644 --- a/test/ManutenabilityMeasure.spec.ts +++ b/test/ManutenabilityMeasure.spec.ts @@ -1,5 +1,5 @@ -import { ManutenabilityMeasure } from '@/domain/measures/ManutenabilityMeasure'; -import { QualityMeasuresValues } from '@/domain/measures/QualityMeasures'; +import { ManutenabilityMeasure } from '@/domain/health-score/measures/ManutenabilityMeasure'; +import { QualityMeasuresValues } from '@/domain/health-score/measures/QualityMeasures'; describe('ManutenabilityMeasure', () => { it('should return A quality ratio for value 1', () => { diff --git a/test/ReliabilityMeasure.spec.ts b/test/ReliabilityMeasure.spec.ts index f8c2ab7..299c6aa 100644 --- a/test/ReliabilityMeasure.spec.ts +++ b/test/ReliabilityMeasure.spec.ts @@ -1,5 +1,5 @@ -import { QualityMeasuresValues } from '@/domain/measures/QualityMeasures'; -import { ReliabilityMeasure } from '@/domain/measures/ReliabilityMeasure'; +import { QualityMeasuresValues } from '@/domain/health-score/measures/QualityMeasures'; +import { ReliabilityMeasure } from '@/domain/health-score/measures/ReliabilityMeasure'; describe('ReliabilityMeasure', () => { it('should return the correct quality ratio for a given value', () => { diff --git a/test/SecurityRatingMeasure.spec.ts b/test/SecurityRatingMeasure.spec.ts index d92ab62..fcd0211 100644 --- a/test/SecurityRatingMeasure.spec.ts +++ b/test/SecurityRatingMeasure.spec.ts @@ -1,5 +1,5 @@ -import { QualityMeasuresValues } from '@/domain/measures/QualityMeasures'; -import { SecurityRatingMeasure } from '@/domain/measures/SecurityRatingMeasure'; +import { QualityMeasuresValues } from '@/domain/health-score/measures/QualityMeasures'; +import { SecurityRatingMeasure } from '@/domain/health-score/measures/SecurityRatingMeasure'; describe('SecurityRatingMeasure', () => { it('should return the correct quality ratio for a given value', () => { diff --git a/test/SecurityReviewMeasure.spec.ts b/test/SecurityReviewMeasure.spec.ts index b297b71..a5c8cea 100644 --- a/test/SecurityReviewMeasure.spec.ts +++ b/test/SecurityReviewMeasure.spec.ts @@ -1,5 +1,5 @@ -import { QualityMeasuresValues } from '@/domain/measures/QualityMeasures'; -import { SecurityReviewMeasure } from '@/domain/measures/SecurityReviewMeasure'; +import { QualityMeasuresValues } from '@/domain/health-score/measures/QualityMeasures'; +import { SecurityReviewMeasure } from '@/domain/health-score/measures/SecurityReviewMeasure'; describe('SecurityReviewMeasure', () => { it('should return the correct quality ratio for a given value', () => { diff --git a/test/TechnicalDebt.spec.ts b/test/TechnicalDebt.spec.ts new file mode 100644 index 0000000..5a0da9b --- /dev/null +++ b/test/TechnicalDebt.spec.ts @@ -0,0 +1,23 @@ +import { TechnicalDebt } from '@/domain/technical-debt/TechnicalDebt'; + +describe('TechnicalDebt', () => { + it('should return correct technical debt in hours for a given value', () => { + const technicalDebt = new TechnicalDebt(120); + expect(technicalDebt.calculateTechnicalDebt()).toBe(2); + }); + + it('should return 0 if the value is 0', () => { + const technicalDebt = new TechnicalDebt(0); + expect(technicalDebt.calculateTechnicalDebt()).toBe(0); + }); + + it('should handle large values correctly', () => { + const technicalDebt = new TechnicalDebt(6000); + expect(technicalDebt.calculateTechnicalDebt()).toBe(100); + }); + + it('should return fractional hours for values not divisible by 60', () => { + const technicalDebt = new TechnicalDebt(90); + expect(technicalDebt.calculateTechnicalDebt()).toBe(1.5); + }); +});