From a3b5d3901c98fc33b389bacb5a36abc216d91ec2 Mon Sep 17 00:00:00 2001 From: hiuyear Date: Tue, 19 May 2026 23:52:01 -0400 Subject: [PATCH 01/12] feat: add getFilesWithViolations use case scaffold and update gitignore --- .gitignore | 5 ++++- .../getFilesWithViolationsInputBoundary.ts | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInputBoundary.ts diff --git a/.gitignore b/.gitignore index 7c947b66..2add7052 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,7 @@ clean-architecture-visualizer/frontend/public/ # Secrets .DS_Store -.env \ No newline at end of file +.env + +# Local task tracking +tasks/ \ No newline at end of file diff --git a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInputBoundary.ts b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInputBoundary.ts new file mode 100644 index 00000000..81c4f286 --- /dev/null +++ b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInputBoundary.ts @@ -0,0 +1,3 @@ +export interface GetFilesWithViolationsInputBoundary { + execute(): Promise; +} \ No newline at end of file From 93c8c0e7b26f44e4ac8227ef3203c8a6aabc13f3 Mon Sep 17 00:00:00 2001 From: hiuyear Date: Tue, 9 Jun 2026 15:41:20 -0400 Subject: [PATCH 02/12] feat: add getFilesWithViolations use case files (WIP) --- .../getFilesWithViolationsController.ts | 0 .../getFilesWithViolationsPresenter.ts | 0 .../getFilesWithViolationsInteractor.ts | 0 .../getFilesWithViolationsOutputBoundary.ts | 3 +++ .../getFilesWithViolationsOutputData.ts | 15 +++++++++++++++ .../use_cases/getFilesWithViolations.test.ts | 0 6 files changed, 18 insertions(+) create mode 100644 clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsController.ts create mode 100644 clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsPresenter.ts create mode 100644 clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts create mode 100644 clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputBoundary.ts create mode 100644 clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.ts create mode 100644 clean-architecture-visualizer/tests/backend/use_cases/getFilesWithViolations.test.ts diff --git a/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsController.ts b/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsController.ts new file mode 100644 index 00000000..e69de29b diff --git a/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsPresenter.ts b/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsPresenter.ts new file mode 100644 index 00000000..e69de29b diff --git a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts new file mode 100644 index 00000000..e69de29b diff --git a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputBoundary.ts b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputBoundary.ts new file mode 100644 index 00000000..e6b7673b --- /dev/null +++ b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputBoundary.ts @@ -0,0 +1,3 @@ +export interface GetFilesWithViolationsOutputBoundary { + getOutputData(): object; +} \ No newline at end of file diff --git a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.ts b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.ts new file mode 100644 index 00000000..cbfd936a --- /dev/null +++ b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.ts @@ -0,0 +1,15 @@ +export class GetFilesWithViolationsOutputData { + + private filesWithViolationsOutputData?: { [key: string]: any }[]; + + // called by interactor to store result + setOutputData(OutputData: {[key: string]: any }[]) { + this.filesWithViolationsOutputData = OutputData; + } + + // called by presenter + getOutputData(): object { + if (this.filesWithViolationsOutputData) return this.filesWithViolationsOutputData; + return {}; + } +} \ No newline at end of file diff --git a/clean-architecture-visualizer/tests/backend/use_cases/getFilesWithViolations.test.ts b/clean-architecture-visualizer/tests/backend/use_cases/getFilesWithViolations.test.ts new file mode 100644 index 00000000..e69de29b From 7368941bfaaf72b534ab57295fc71b8add4830c4 Mon Sep 17 00:00:00 2001 From: hiuyear Date: Wed, 10 Jun 2026 13:41:36 -0400 Subject: [PATCH 03/12] interactor --- .../getFilesWithViolationsInteractor.ts | 30 +++++++++++++++++++ .../getFilesWithViolationsOutputData.ts | 4 +-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts index e69de29b..f2b383d6 100644 --- a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts +++ b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts @@ -0,0 +1,30 @@ +import type { GetFilesWithViolationsInputBoundary} from "./getFilesWithViolationsInputBoundary.js" +import type { SessionDBAccessInterface } from "../../data_access/sessionDBAccessInterface.js"; +import type { GetFilesWithViolationsOutputData } from "./getFilesWithViolationsOutputData.js"; + +export class GetFileswithViolationInteractor implements GetFilesWithViolationsInputBoundary { + + // new per session. construcing REQUIRES input of BOTH DBA and Output data (blank) + constructor ( + private readonly db: SessionDBAccessInterface, + private readonly outputdata: GetFilesWithViolationsOutputData, + ){} + + async execute(): Promise { + + // - getNodesByStatus("VIOLATION") + const violatingNodes = this.db.getNodesByStatus("VIOLATION"); + + // - map to filePaths + const violatingNodesPaths = violatingNodes.map((node) => node.filePath); + + // - remove nodes with undefined filepatrhs + const filteredViolatingNodePaths = violatingNodesPaths.filter((path): path is string => path != undefined) + + // - deduplicate with Set + const uniqueNodesPaths = [...new Set(filteredViolatingNodePaths)]; + + // - setOutputData({ total_violations, files }) + this.outputdata.setOutputData({ total_violations: violatingNodes.length, files: uniqueNodesPaths}) + } +} \ No newline at end of file diff --git a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.ts b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.ts index cbfd936a..8566864c 100644 --- a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.ts +++ b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.ts @@ -1,9 +1,9 @@ export class GetFilesWithViolationsOutputData { - private filesWithViolationsOutputData?: { [key: string]: any }[]; + private filesWithViolationsOutputData?: { [key: string]: any }; // called by interactor to store result - setOutputData(OutputData: {[key: string]: any }[]) { + setOutputData(OutputData: {[key: string]: any }) { this.filesWithViolationsOutputData = OutputData; } From b03c4b6320c3b3ab1c3008e43d95d1bc0ad65b04 Mon Sep 17 00:00:00 2001 From: hiuyear Date: Wed, 10 Jun 2026 13:42:27 -0400 Subject: [PATCH 04/12] fixed camel case naming bug --- .../getFilesWithViolations/getFilesWithViolationsInteractor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts index f2b383d6..af1cb0c7 100644 --- a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts +++ b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts @@ -2,7 +2,7 @@ import type { GetFilesWithViolationsInputBoundary} from "./getFilesWithViolation import type { SessionDBAccessInterface } from "../../data_access/sessionDBAccessInterface.js"; import type { GetFilesWithViolationsOutputData } from "./getFilesWithViolationsOutputData.js"; -export class GetFileswithViolationInteractor implements GetFilesWithViolationsInputBoundary { +export class GetFilesWithViolationInteractor implements GetFilesWithViolationsInputBoundary { // new per session. construcing REQUIRES input of BOTH DBA and Output data (blank) constructor ( From 8ffad065013488ff511171f792cb966c2649b3b5 Mon Sep 17 00:00:00 2001 From: hiuyear Date: Wed, 10 Jun 2026 14:27:29 -0400 Subject: [PATCH 05/12] presenter --- .../getFilesWithViolationsPresenter.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsPresenter.ts b/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsPresenter.ts index e69de29b..97abb5b8 100644 --- a/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsPresenter.ts +++ b/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsPresenter.ts @@ -0,0 +1,13 @@ +import type {GetFilesWithViolationsOutputBoundary} from "../../use_case/getFilesWithViolations/getFilesWithViolationsOutputBoundary.js" +import type { GetFilesWithViolationsOutputData } from "../../use_case/getFilesWithViolations/getFilesWithViolationsOutputData.js" + +export class GetFilesWithViolationsPresenter implements GetFilesWithViolationsOutputBoundary { + + constructor( + private readonly outputData: GetFilesWithViolationsOutputData + ){} + + getOutputData(){ + return this.outputData.getOutputData(); + } +} \ No newline at end of file From b4c01ab02dab9e80120b0cb656439c30296419a4 Mon Sep 17 00:00:00 2001 From: hiuyear Date: Wed, 10 Jun 2026 15:09:34 -0400 Subject: [PATCH 06/12] controller --- .../getFilesWithViolationsController.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsController.ts b/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsController.ts index e69de29b..977a66a2 100644 --- a/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsController.ts +++ b/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsController.ts @@ -0,0 +1,12 @@ +import type {GetFilesWithViolationsInputBoundary} from "../../use_case/getFilesWithViolations/getFilesWithViolationsInputBoundary.js" + +export class GetFilesWithViolationsController { + + constructor( + private readonly inputboundary: GetFilesWithViolationsInputBoundary + ){} + + async execute(): Promise { + await this.inputboundary.execute() + } +} \ No newline at end of file From 960fb3e0afa8f11c440c0fc251509f3400ba41dd Mon Sep 17 00:00:00 2001 From: hiuyear Date: Wed, 10 Jun 2026 15:13:43 -0400 Subject: [PATCH 07/12] bug: fixed naming errors --- .../getFilesWithViolationsInteractor.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts index af1cb0c7..ff61f3aa 100644 --- a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts +++ b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts @@ -2,9 +2,8 @@ import type { GetFilesWithViolationsInputBoundary} from "./getFilesWithViolation import type { SessionDBAccessInterface } from "../../data_access/sessionDBAccessInterface.js"; import type { GetFilesWithViolationsOutputData } from "./getFilesWithViolationsOutputData.js"; -export class GetFilesWithViolationInteractor implements GetFilesWithViolationsInputBoundary { +export class GetFilesWithViolationsInteractor implements GetFilesWithViolationsInputBoundary { - // new per session. construcing REQUIRES input of BOTH DBA and Output data (blank) constructor ( private readonly db: SessionDBAccessInterface, private readonly outputdata: GetFilesWithViolationsOutputData, @@ -12,19 +11,14 @@ export class GetFilesWithViolationInteractor implements GetFilesWithViolationsIn async execute(): Promise { - // - getNodesByStatus("VIOLATION") const violatingNodes = this.db.getNodesByStatus("VIOLATION"); - // - map to filePaths const violatingNodesPaths = violatingNodes.map((node) => node.filePath); - // - remove nodes with undefined filepatrhs const filteredViolatingNodePaths = violatingNodesPaths.filter((path): path is string => path != undefined) - // - deduplicate with Set const uniqueNodesPaths = [...new Set(filteredViolatingNodePaths)]; - // - setOutputData({ total_violations, files }) this.outputdata.setOutputData({ total_violations: violatingNodes.length, files: uniqueNodesPaths}) } } \ No newline at end of file From 4d535de4d0711d49784b38630c8120c801966dd7 Mon Sep 17 00:00:00 2001 From: hiuyear Date: Thu, 25 Jun 2026 02:26:11 -0400 Subject: [PATCH 08/12] feat: add routes --- .../src/server/routes/analysis.ts | 16 ++++++++++++++++ .../use_cases/getFilesWithViolations.test.ts | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/clean-architecture-visualizer/src/server/routes/analysis.ts b/clean-architecture-visualizer/src/server/routes/analysis.ts index 52253864..567595e9 100644 --- a/clean-architecture-visualizer/src/server/routes/analysis.ts +++ b/clean-architecture-visualizer/src/server/routes/analysis.ts @@ -15,6 +15,10 @@ import { GetViolationsInteractor } from "../../use_case/getViolations/GetViolati import { FileAccess } from "../../data_access/fileAccess.js"; import { GetViolationsController } from "../../interface_adapter/getViolations/getViolationsController.js"; import { GetViolationsPresenter } from "../../interface_adapter/getViolations/getViolationsPresenter.js"; +import { GetFilesWithViolationsOutputData } from "../../use_case/getFilesWithViolations/getFilesWithViolationsOutputData.js"; +import { GetFilesWithViolationsInteractor } from "../../use_case/getFilesWithViolations/getFilesWithViolationsInteractor.js"; +import { GetFilesWithViolationsController } from "../../interface_adapter/getFilesWithViolations/getFilesWithViolationsController.js"; +import { GetFilesWithViolationsPresenter } from "../../interface_adapter/getFilesWithViolations/getFilesWithViolationsPresenter.js"; const router = Router(); @@ -74,4 +78,16 @@ router.get("/analysis/violations/:interactionId", async (req, res) => { res.json(result); }); +router.get("/analysis/files-with-violations", (_req, res) => { + const outputData = new GetFilesWithViolationsOutputData(); + const interactor = new GetFilesWithViolationsInteractor(dbAccess, outputData); + const controller = new GetFilesWithViolationsController(interactor); + const presenter = new GetFilesWithViolationsPresenter(outputData); + + controller.execute(); + const result = presenter.getOutputData(); + + res.json(result); +}); + export default router; \ No newline at end of file diff --git a/clean-architecture-visualizer/tests/backend/use_cases/getFilesWithViolations.test.ts b/clean-architecture-visualizer/tests/backend/use_cases/getFilesWithViolations.test.ts index e69de29b..3cadd106 100644 --- a/clean-architecture-visualizer/tests/backend/use_cases/getFilesWithViolations.test.ts +++ b/clean-architecture-visualizer/tests/backend/use_cases/getFilesWithViolations.test.ts @@ -0,0 +1,19 @@ +import { describe, it, expect, beforeEach} from '@jest/globals'; +import { GetFilesWithViolationsInteractor } from "../../../src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.js"; +import { SessionDBAccess } from "../../../src/data_access/sessionDBAccess.js"; +import { useCaseGraph } from '../../../src/entity/useCaseGraph.js'; +import type { GetFilesWithViolationsOutputData } from "../../../src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.js"; +import type { SessionData } from '../../../src/types/sessionData.js'; +import type { neighbourMap } from '../../../src/types/neighbourMap.js'; +import type { cleanNode } from '../../../src/types/cleanNode.js'; + +const genericDBAccess = new SessionDBAccess(); + +// Standardized mock for Output Data matching your interface +function makeOutputData(): GetFilesWithViolationsOutputData & { result: any } { + return { + result: undefined, + setOutputData(data: any) { this.result = data; }, + getOutputData() { return this.result; } + } as GetFilesWithViolationsOutputData & { result: any }; +} From 390da4a80fda4d605fa799b8ad78482fd9dcf4ec Mon Sep 17 00:00:00 2001 From: hiuyear Date: Tue, 7 Jul 2026 12:15:06 -0400 Subject: [PATCH 09/12] test: add getFilesWithViolations interactor tests --- .../use_cases/getFilesWithViolations.test.ts | 171 +++++++++++++++++- 1 file changed, 165 insertions(+), 6 deletions(-) diff --git a/clean-architecture-visualizer/tests/backend/use_cases/getFilesWithViolations.test.ts b/clean-architecture-visualizer/tests/backend/use_cases/getFilesWithViolations.test.ts index 3cadd106..9d678290 100644 --- a/clean-architecture-visualizer/tests/backend/use_cases/getFilesWithViolations.test.ts +++ b/clean-architecture-visualizer/tests/backend/use_cases/getFilesWithViolations.test.ts @@ -1,15 +1,10 @@ -import { describe, it, expect, beforeEach} from '@jest/globals'; +import { describe, it, expect, beforeEach } from '@jest/globals'; import { GetFilesWithViolationsInteractor } from "../../../src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.js"; import { SessionDBAccess } from "../../../src/data_access/sessionDBAccess.js"; -import { useCaseGraph } from '../../../src/entity/useCaseGraph.js'; import type { GetFilesWithViolationsOutputData } from "../../../src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.js"; -import type { SessionData } from '../../../src/types/sessionData.js'; -import type { neighbourMap } from '../../../src/types/neighbourMap.js'; -import type { cleanNode } from '../../../src/types/cleanNode.js'; const genericDBAccess = new SessionDBAccess(); -// Standardized mock for Output Data matching your interface function makeOutputData(): GetFilesWithViolationsOutputData & { result: any } { return { result: undefined, @@ -17,3 +12,167 @@ function makeOutputData(): GetFilesWithViolationsOutputData & { result: any } { getOutputData() { return this.result; } } as GetFilesWithViolationsOutputData & { result: any }; } + +describe("GetFilesWithViolationsInteractor", () => { + + beforeEach(() => { + genericDBAccess.resetDB(); + }); + + it("returns the file paths of nodes with VIOLATION status", async () => { + genericDBAccess.upsertNode({ + id: "node-1", + type: "entities", + layer: "enterpriseBusinessRules", + filePath: "src/entities/Student.java", + status: "VIOLATION" + }); + genericDBAccess.upsertNode({ + id: "node-2", + type: "useCaseInteractor", + layer: "applicationBusinessRules", + filePath: "src/use_case/EnrollStudent.java", + status: "VIOLATION" + }); + + const outputData = makeOutputData(); + const interactor = new GetFilesWithViolationsInteractor(genericDBAccess, outputData); + + await interactor.execute(); + + expect(outputData.result.files).toEqual( + expect.arrayContaining(["src/entities/Student.java", "src/use_case/EnrollStudent.java"]) + ); + expect(outputData.result.files).toHaveLength(2); + }); + + it("excludes nodes that are not in VIOLATION status", async () => { + genericDBAccess.upsertNode({ + id: "node-bad", + type: "controller", + layer: "interfaceAdapters", + filePath: "src/interface_adapters/BadController.java", + status: "VIOLATION" + }); + genericDBAccess.upsertNode({ + id: "node-valid", + type: "controller", + layer: "interfaceAdapters", + filePath: "src/interface_adapters/GoodController.java", + status: "VALID" + }); + genericDBAccess.upsertNode({ + id: "node-missing", + type: "presenter", + layer: "interfaceAdapters", + status: "MISSING" + }); + + const outputData = makeOutputData(); + const interactor = new GetFilesWithViolationsInteractor(genericDBAccess, outputData); + + await interactor.execute(); + + expect(outputData.result.files).toEqual(["src/interface_adapters/BadController.java"]); + }); + + it("deduplicates file paths when multiple violation nodes share a file", async () => { + const filePath = "src/use_case/EnrollStudent.java"; + + genericDBAccess.upsertNode({ + id: "node-1", + type: "useCaseInteractor", + layer: "applicationBusinessRules", + filePath: filePath, + status: "VIOLATION" + }); + genericDBAccess.upsertNode({ + id: "node-2", + type: "inputBoundary", + layer: "applicationBusinessRules", + filePath: filePath, + status: "VIOLATION" + }); + + const outputData = makeOutputData(); + const interactor = new GetFilesWithViolationsInteractor(genericDBAccess, outputData); + + await interactor.execute(); + + expect(outputData.result.files).toEqual([filePath]); + }); + + it("filters out violation nodes that have no file path", async () => { + genericDBAccess.upsertNode({ + id: "node-real", + type: "entities", + layer: "enterpriseBusinessRules", + filePath: "src/entities/Student.java", + status: "VIOLATION" + }); + genericDBAccess.upsertNode({ + id: "node-missing", + type: "controller", + layer: "interfaceAdapters", + status: "VIOLATION" + }); + + const outputData = makeOutputData(); + const interactor = new GetFilesWithViolationsInteractor(genericDBAccess, outputData); + + await interactor.execute(); + + expect(outputData.result.files).toEqual(["src/entities/Student.java"]); + }); + + it("returns an empty files array when there are no violations", async () => { + genericDBAccess.upsertNode({ + id: "node-valid", + type: "controller", + layer: "interfaceAdapters", + filePath: "src/interface_adapters/GoodController.java", + status: "VALID" + }); + + const outputData = makeOutputData(); + const interactor = new GetFilesWithViolationsInteractor(genericDBAccess, outputData); + + await interactor.execute(); + + expect(outputData.result.files).toEqual([]); + expect(outputData.result.total_violations).toBe(0); + }); + + it("counts total_violations by node, not by unique file", async () => { + const filePath = "src/use_case/EnrollStudent.java"; + + genericDBAccess.upsertNode({ + id: "node-1", + type: "useCaseInteractor", + layer: "applicationBusinessRules", + filePath: filePath, + status: "VIOLATION" + }); + genericDBAccess.upsertNode({ + id: "node-2", + type: "inputBoundary", + layer: "applicationBusinessRules", + filePath: filePath, + status: "VIOLATION" + }); + genericDBAccess.upsertNode({ + id: "node-3", + type: "controller", + layer: "interfaceAdapters", + status: "VIOLATION" + }); + + const outputData = makeOutputData(); + const interactor = new GetFilesWithViolationsInteractor(genericDBAccess, outputData); + + await interactor.execute(); + + expect(outputData.result.total_violations).toBe(3); + expect(outputData.result.files).toHaveLength(1); + }); +}); From 507083d700323f37022a04f6d87090a94bcbe679 Mon Sep 17 00:00:00 2001 From: hiuyear Date: Tue, 7 Jul 2026 12:15:43 -0400 Subject: [PATCH 10/12] refactor: remove redundant comments from getFilesWithViolations output data --- .../getFilesWithViolations/getFilesWithViolationsOutputData.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.ts b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.ts index 8566864c..3b6ac68b 100644 --- a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.ts +++ b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.ts @@ -2,12 +2,10 @@ export class GetFilesWithViolationsOutputData { private filesWithViolationsOutputData?: { [key: string]: any }; - // called by interactor to store result setOutputData(OutputData: {[key: string]: any }) { this.filesWithViolationsOutputData = OutputData; } - // called by presenter getOutputData(): object { if (this.filesWithViolationsOutputData) return this.filesWithViolationsOutputData; return {}; From 173378108c2746fe6d69c70828d221b24108a450 Mon Sep 17 00:00:00 2001 From: hiuyear Date: Tue, 14 Jul 2026 11:45:47 -0400 Subject: [PATCH 11/12] docs: add Hiu Yan Kwok to CONTRIBUTORS.md --- CONTRIBUTORS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 196c2be5..baa84c5b 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -4,10 +4,11 @@ Annie Wang, Cindy Yang, Ethan Diep, Eric Xu, +Hiu Yan Kwok, Jayden Ong Matthew Dahlgren, Olivier Denis, Raymond Wu, Seokjin Yoo, Vithu Thayalan, -Vivian Deng +Vivian Deng \ No newline at end of file From b9fa7d31872198ff3ffc7450b2530b1f6bc9714a Mon Sep 17 00:00:00 2001 From: hiuyear Date: Tue, 14 Jul 2026 13:16:47 -0400 Subject: [PATCH 12/12] style: format getFilesWithViolations files for CI --- CONTRIBUTORS.md | 2 +- .../getFilesWithViolationsController.ts | 17 +- .../getFilesWithViolationsPresenter.ts | 17 +- .../getFilesWithViolationsInputBoundary.ts | 4 +- .../getFilesWithViolationsInteractor.ts | 37 +- .../getFilesWithViolationsOutputBoundary.ts | 4 +- .../getFilesWithViolationsOutputData.ts | 20 +- .../use_cases/getFilesWithViolations.test.ts | 336 ++++++++++-------- 8 files changed, 231 insertions(+), 206 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index baa84c5b..206507eb 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -11,4 +11,4 @@ Olivier Denis, Raymond Wu, Seokjin Yoo, Vithu Thayalan, -Vivian Deng \ No newline at end of file +Vivian Deng diff --git a/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsController.ts b/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsController.ts index 977a66a2..fb429539 100644 --- a/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsController.ts +++ b/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsController.ts @@ -1,12 +1,11 @@ -import type {GetFilesWithViolationsInputBoundary} from "../../use_case/getFilesWithViolations/getFilesWithViolationsInputBoundary.js" +import type { GetFilesWithViolationsInputBoundary } from '../../use_case/getFilesWithViolations/getFilesWithViolationsInputBoundary.js'; export class GetFilesWithViolationsController { - - constructor( - private readonly inputboundary: GetFilesWithViolationsInputBoundary - ){} + constructor( + private readonly inputboundary: GetFilesWithViolationsInputBoundary + ) {} - async execute(): Promise { - await this.inputboundary.execute() - } -} \ No newline at end of file + async execute(): Promise { + await this.inputboundary.execute(); + } +} diff --git a/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsPresenter.ts b/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsPresenter.ts index 97abb5b8..5d2e7d2b 100644 --- a/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsPresenter.ts +++ b/clean-architecture-visualizer/src/interface_adapter/getFilesWithViolations/getFilesWithViolationsPresenter.ts @@ -1,13 +1,10 @@ -import type {GetFilesWithViolationsOutputBoundary} from "../../use_case/getFilesWithViolations/getFilesWithViolationsOutputBoundary.js" -import type { GetFilesWithViolationsOutputData } from "../../use_case/getFilesWithViolations/getFilesWithViolationsOutputData.js" +import type { GetFilesWithViolationsOutputBoundary } from '../../use_case/getFilesWithViolations/getFilesWithViolationsOutputBoundary.js'; +import type { GetFilesWithViolationsOutputData } from '../../use_case/getFilesWithViolations/getFilesWithViolationsOutputData.js'; export class GetFilesWithViolationsPresenter implements GetFilesWithViolationsOutputBoundary { + constructor(private readonly outputData: GetFilesWithViolationsOutputData) {} - constructor( - private readonly outputData: GetFilesWithViolationsOutputData - ){} - - getOutputData(){ - return this.outputData.getOutputData(); - } -} \ No newline at end of file + getOutputData() { + return this.outputData.getOutputData(); + } +} diff --git a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInputBoundary.ts b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInputBoundary.ts index 81c4f286..cfbbab4e 100644 --- a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInputBoundary.ts +++ b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInputBoundary.ts @@ -1,3 +1,3 @@ export interface GetFilesWithViolationsInputBoundary { - execute(): Promise; -} \ No newline at end of file + execute(): Promise; +} diff --git a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts index ff61f3aa..07697da1 100644 --- a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts +++ b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.ts @@ -1,24 +1,27 @@ -import type { GetFilesWithViolationsInputBoundary} from "./getFilesWithViolationsInputBoundary.js" -import type { SessionDBAccessInterface } from "../../data_access/sessionDBAccessInterface.js"; -import type { GetFilesWithViolationsOutputData } from "./getFilesWithViolationsOutputData.js"; +import type { GetFilesWithViolationsInputBoundary } from './getFilesWithViolationsInputBoundary.js'; +import type { SessionDBAccessInterface } from '../../data_access/sessionDBAccessInterface.js'; +import type { GetFilesWithViolationsOutputData } from './getFilesWithViolationsOutputData.js'; export class GetFilesWithViolationsInteractor implements GetFilesWithViolationsInputBoundary { - - constructor ( - private readonly db: SessionDBAccessInterface, - private readonly outputdata: GetFilesWithViolationsOutputData, - ){} + constructor( + private readonly db: SessionDBAccessInterface, + private readonly outputdata: GetFilesWithViolationsOutputData + ) {} - async execute(): Promise { + async execute(): Promise { + const violatingNodes = this.db.getNodesByStatus('VIOLATION'); - const violatingNodes = this.db.getNodesByStatus("VIOLATION"); + const violatingNodesPaths = violatingNodes.map((node) => node.filePath); - const violatingNodesPaths = violatingNodes.map((node) => node.filePath); - - const filteredViolatingNodePaths = violatingNodesPaths.filter((path): path is string => path != undefined) + const filteredViolatingNodePaths = violatingNodesPaths.filter( + (path): path is string => path != undefined + ); - const uniqueNodesPaths = [...new Set(filteredViolatingNodePaths)]; + const uniqueNodesPaths = [...new Set(filteredViolatingNodePaths)]; - this.outputdata.setOutputData({ total_violations: violatingNodes.length, files: uniqueNodesPaths}) - } -} \ No newline at end of file + this.outputdata.setOutputData({ + total_violations: violatingNodes.length, + files: uniqueNodesPaths, + }); + } +} diff --git a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputBoundary.ts b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputBoundary.ts index e6b7673b..044185fd 100644 --- a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputBoundary.ts +++ b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputBoundary.ts @@ -1,3 +1,3 @@ export interface GetFilesWithViolationsOutputBoundary { - getOutputData(): object; -} \ No newline at end of file + getOutputData(): object; +} diff --git a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.ts b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.ts index 3b6ac68b..c8ce2b74 100644 --- a/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.ts +++ b/clean-architecture-visualizer/src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.ts @@ -1,13 +1,13 @@ export class GetFilesWithViolationsOutputData { - - private filesWithViolationsOutputData?: { [key: string]: any }; + private filesWithViolationsOutputData?: { [key: string]: any }; - setOutputData(OutputData: {[key: string]: any }) { - this.filesWithViolationsOutputData = OutputData; - } + setOutputData(OutputData: { [key: string]: any }) { + this.filesWithViolationsOutputData = OutputData; + } - getOutputData(): object { - if (this.filesWithViolationsOutputData) return this.filesWithViolationsOutputData; - return {}; - } -} \ No newline at end of file + getOutputData(): object { + if (this.filesWithViolationsOutputData) + return this.filesWithViolationsOutputData; + return {}; + } +} diff --git a/clean-architecture-visualizer/tests/backend/use_cases/getFilesWithViolations.test.ts b/clean-architecture-visualizer/tests/backend/use_cases/getFilesWithViolations.test.ts index 9d678290..5a1cb48c 100644 --- a/clean-architecture-visualizer/tests/backend/use_cases/getFilesWithViolations.test.ts +++ b/clean-architecture-visualizer/tests/backend/use_cases/getFilesWithViolations.test.ts @@ -1,178 +1,204 @@ import { describe, it, expect, beforeEach } from '@jest/globals'; -import { GetFilesWithViolationsInteractor } from "../../../src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.js"; -import { SessionDBAccess } from "../../../src/data_access/sessionDBAccess.js"; -import type { GetFilesWithViolationsOutputData } from "../../../src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.js"; +import { GetFilesWithViolationsInteractor } from '../../../src/use_case/getFilesWithViolations/getFilesWithViolationsInteractor.js'; +import { SessionDBAccess } from '../../../src/data_access/sessionDBAccess.js'; +import type { GetFilesWithViolationsOutputData } from '../../../src/use_case/getFilesWithViolations/getFilesWithViolationsOutputData.js'; const genericDBAccess = new SessionDBAccess(); function makeOutputData(): GetFilesWithViolationsOutputData & { result: any } { - return { - result: undefined, - setOutputData(data: any) { this.result = data; }, - getOutputData() { return this.result; } - } as GetFilesWithViolationsOutputData & { result: any }; + return { + result: undefined, + setOutputData(data: any) { + this.result = data; + }, + getOutputData() { + return this.result; + }, + } as GetFilesWithViolationsOutputData & { result: any }; } -describe("GetFilesWithViolationsInteractor", () => { - - beforeEach(() => { - genericDBAccess.resetDB(); +describe('GetFilesWithViolationsInteractor', () => { + beforeEach(() => { + genericDBAccess.resetDB(); + }); + + it('returns the file paths of nodes with VIOLATION status', async () => { + genericDBAccess.upsertNode({ + id: 'node-1', + type: 'entities', + layer: 'enterpriseBusinessRules', + filePath: 'src/entities/Student.java', + status: 'VIOLATION', }); - - it("returns the file paths of nodes with VIOLATION status", async () => { - genericDBAccess.upsertNode({ - id: "node-1", - type: "entities", - layer: "enterpriseBusinessRules", - filePath: "src/entities/Student.java", - status: "VIOLATION" - }); - genericDBAccess.upsertNode({ - id: "node-2", - type: "useCaseInteractor", - layer: "applicationBusinessRules", - filePath: "src/use_case/EnrollStudent.java", - status: "VIOLATION" - }); - - const outputData = makeOutputData(); - const interactor = new GetFilesWithViolationsInteractor(genericDBAccess, outputData); - - await interactor.execute(); - - expect(outputData.result.files).toEqual( - expect.arrayContaining(["src/entities/Student.java", "src/use_case/EnrollStudent.java"]) - ); - expect(outputData.result.files).toHaveLength(2); + genericDBAccess.upsertNode({ + id: 'node-2', + type: 'useCaseInteractor', + layer: 'applicationBusinessRules', + filePath: 'src/use_case/EnrollStudent.java', + status: 'VIOLATION', }); - it("excludes nodes that are not in VIOLATION status", async () => { - genericDBAccess.upsertNode({ - id: "node-bad", - type: "controller", - layer: "interfaceAdapters", - filePath: "src/interface_adapters/BadController.java", - status: "VIOLATION" - }); - genericDBAccess.upsertNode({ - id: "node-valid", - type: "controller", - layer: "interfaceAdapters", - filePath: "src/interface_adapters/GoodController.java", - status: "VALID" - }); - genericDBAccess.upsertNode({ - id: "node-missing", - type: "presenter", - layer: "interfaceAdapters", - status: "MISSING" - }); - - const outputData = makeOutputData(); - const interactor = new GetFilesWithViolationsInteractor(genericDBAccess, outputData); - - await interactor.execute(); - - expect(outputData.result.files).toEqual(["src/interface_adapters/BadController.java"]); + const outputData = makeOutputData(); + const interactor = new GetFilesWithViolationsInteractor( + genericDBAccess, + outputData + ); + + await interactor.execute(); + + expect(outputData.result.files).toEqual( + expect.arrayContaining([ + 'src/entities/Student.java', + 'src/use_case/EnrollStudent.java', + ]) + ); + expect(outputData.result.files).toHaveLength(2); + }); + + it('excludes nodes that are not in VIOLATION status', async () => { + genericDBAccess.upsertNode({ + id: 'node-bad', + type: 'controller', + layer: 'interfaceAdapters', + filePath: 'src/interface_adapters/BadController.java', + status: 'VIOLATION', + }); + genericDBAccess.upsertNode({ + id: 'node-valid', + type: 'controller', + layer: 'interfaceAdapters', + filePath: 'src/interface_adapters/GoodController.java', + status: 'VALID', }); + genericDBAccess.upsertNode({ + id: 'node-missing', + type: 'presenter', + layer: 'interfaceAdapters', + status: 'MISSING', + }); + + const outputData = makeOutputData(); + const interactor = new GetFilesWithViolationsInteractor( + genericDBAccess, + outputData + ); + + await interactor.execute(); - it("deduplicates file paths when multiple violation nodes share a file", async () => { - const filePath = "src/use_case/EnrollStudent.java"; - - genericDBAccess.upsertNode({ - id: "node-1", - type: "useCaseInteractor", - layer: "applicationBusinessRules", - filePath: filePath, - status: "VIOLATION" - }); - genericDBAccess.upsertNode({ - id: "node-2", - type: "inputBoundary", - layer: "applicationBusinessRules", - filePath: filePath, - status: "VIOLATION" - }); - - const outputData = makeOutputData(); - const interactor = new GetFilesWithViolationsInteractor(genericDBAccess, outputData); - - await interactor.execute(); - - expect(outputData.result.files).toEqual([filePath]); + expect(outputData.result.files).toEqual([ + 'src/interface_adapters/BadController.java', + ]); + }); + + it('deduplicates file paths when multiple violation nodes share a file', async () => { + const filePath = 'src/use_case/EnrollStudent.java'; + + genericDBAccess.upsertNode({ + id: 'node-1', + type: 'useCaseInteractor', + layer: 'applicationBusinessRules', + filePath: filePath, + status: 'VIOLATION', + }); + genericDBAccess.upsertNode({ + id: 'node-2', + type: 'inputBoundary', + layer: 'applicationBusinessRules', + filePath: filePath, + status: 'VIOLATION', }); - it("filters out violation nodes that have no file path", async () => { - genericDBAccess.upsertNode({ - id: "node-real", - type: "entities", - layer: "enterpriseBusinessRules", - filePath: "src/entities/Student.java", - status: "VIOLATION" - }); - genericDBAccess.upsertNode({ - id: "node-missing", - type: "controller", - layer: "interfaceAdapters", - status: "VIOLATION" - }); - - const outputData = makeOutputData(); - const interactor = new GetFilesWithViolationsInteractor(genericDBAccess, outputData); - - await interactor.execute(); - - expect(outputData.result.files).toEqual(["src/entities/Student.java"]); + const outputData = makeOutputData(); + const interactor = new GetFilesWithViolationsInteractor( + genericDBAccess, + outputData + ); + + await interactor.execute(); + + expect(outputData.result.files).toEqual([filePath]); + }); + + it('filters out violation nodes that have no file path', async () => { + genericDBAccess.upsertNode({ + id: 'node-real', + type: 'entities', + layer: 'enterpriseBusinessRules', + filePath: 'src/entities/Student.java', + status: 'VIOLATION', + }); + genericDBAccess.upsertNode({ + id: 'node-missing', + type: 'controller', + layer: 'interfaceAdapters', + status: 'VIOLATION', }); - it("returns an empty files array when there are no violations", async () => { - genericDBAccess.upsertNode({ - id: "node-valid", - type: "controller", - layer: "interfaceAdapters", - filePath: "src/interface_adapters/GoodController.java", - status: "VALID" - }); + const outputData = makeOutputData(); + const interactor = new GetFilesWithViolationsInteractor( + genericDBAccess, + outputData + ); - const outputData = makeOutputData(); - const interactor = new GetFilesWithViolationsInteractor(genericDBAccess, outputData); + await interactor.execute(); - await interactor.execute(); + expect(outputData.result.files).toEqual(['src/entities/Student.java']); + }); - expect(outputData.result.files).toEqual([]); - expect(outputData.result.total_violations).toBe(0); + it('returns an empty files array when there are no violations', async () => { + genericDBAccess.upsertNode({ + id: 'node-valid', + type: 'controller', + layer: 'interfaceAdapters', + filePath: 'src/interface_adapters/GoodController.java', + status: 'VALID', }); - it("counts total_violations by node, not by unique file", async () => { - const filePath = "src/use_case/EnrollStudent.java"; - - genericDBAccess.upsertNode({ - id: "node-1", - type: "useCaseInteractor", - layer: "applicationBusinessRules", - filePath: filePath, - status: "VIOLATION" - }); - genericDBAccess.upsertNode({ - id: "node-2", - type: "inputBoundary", - layer: "applicationBusinessRules", - filePath: filePath, - status: "VIOLATION" - }); - genericDBAccess.upsertNode({ - id: "node-3", - type: "controller", - layer: "interfaceAdapters", - status: "VIOLATION" - }); - - const outputData = makeOutputData(); - const interactor = new GetFilesWithViolationsInteractor(genericDBAccess, outputData); - - await interactor.execute(); - - expect(outputData.result.total_violations).toBe(3); - expect(outputData.result.files).toHaveLength(1); + const outputData = makeOutputData(); + const interactor = new GetFilesWithViolationsInteractor( + genericDBAccess, + outputData + ); + + await interactor.execute(); + + expect(outputData.result.files).toEqual([]); + expect(outputData.result.total_violations).toBe(0); + }); + + it('counts total_violations by node, not by unique file', async () => { + const filePath = 'src/use_case/EnrollStudent.java'; + + genericDBAccess.upsertNode({ + id: 'node-1', + type: 'useCaseInteractor', + layer: 'applicationBusinessRules', + filePath: filePath, + status: 'VIOLATION', }); + genericDBAccess.upsertNode({ + id: 'node-2', + type: 'inputBoundary', + layer: 'applicationBusinessRules', + filePath: filePath, + status: 'VIOLATION', + }); + genericDBAccess.upsertNode({ + id: 'node-3', + type: 'controller', + layer: 'interfaceAdapters', + status: 'VIOLATION', + }); + + const outputData = makeOutputData(); + const interactor = new GetFilesWithViolationsInteractor( + genericDBAccess, + outputData + ); + + await interactor.execute(); + + expect(outputData.result.total_violations).toBe(3); + expect(outputData.result.files).toHaveLength(1); + }); });