2 Commits 23d392736e ... 131d089046

Author SHA1 Message Date
  Owen Diffey 131d089046 chore: Enable backend test in workflow 1 month ago
  Owen Diffey 5e7bc71ae1 chore: Add job statistics tests 1 month ago

+ 2 - 2
.github/workflows/automated-tests.yml

@@ -23,7 +23,7 @@ jobs:
                   ./musare.sh build
             - name: Start Musare
               run: ./musare.sh start
-#            - name: Test Backend
-#              run: ./musare.sh test backend
+            - name: Test Backend
+              run: ./musare.sh test backend
             - name: Test Frontend
               run: ./musare.sh test frontend

+ 24 - 1
backend/package-lock.json

@@ -28,6 +28,7 @@
 				"ws": "^8.13.0"
 			},
 			"devDependencies": {
+				"@faker-js/faker": "^8.4.1",
 				"@microsoft/tsdoc": "^0.14.2",
 				"@types/chai": "^4.3.5",
 				"@types/config": "^3.3.0",
@@ -126,6 +127,22 @@
 				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
 			}
 		},
+		"node_modules/@faker-js/faker": {
+			"version": "8.4.1",
+			"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz",
+			"integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "opencollective",
+					"url": "https://opencollective.com/fakerjs"
+				}
+			],
+			"engines": {
+				"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
+				"npm": ">=6.14.13"
+			}
+		},
 		"node_modules/@humanwhocodes/config-array": {
 			"version": "0.11.8",
 			"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
@@ -5634,6 +5651,12 @@
 			"integrity": "sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==",
 			"dev": true
 		},
+		"@faker-js/faker": {
+			"version": "8.4.1",
+			"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz",
+			"integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==",
+			"dev": true
+		},
 		"@humanwhocodes/config-array": {
 			"version": "0.11.8",
 			"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
@@ -9696,4 +9719,4 @@
 			"dev": true
 		}
 	}
-}
+}

+ 3 - 2
backend/package.json

@@ -14,7 +14,7 @@
 		"prod": "node build/app/src/main.js",
 		"lint": "eslint src --ext .js,.ts",
 		"typescript": "tsc --noEmit",
-		"test": "mocha -r ts-node/register -r tsconfig-paths/register 'tests/**/*.test.ts' 'src/**/*.spec.ts'"
+		"test": "mocha -r ts-node/register -r tsconfig-paths/register -r chai/register-should.js 'tests/**/*.test.ts' 'src/**/*.spec.ts'"
 	},
 	"dependencies": {
 		"axios": "^1.4.0",
@@ -36,6 +36,7 @@
 		"ws": "^8.13.0"
 	},
 	"devDependencies": {
+		"@faker-js/faker": "^8.4.1",
 		"@microsoft/tsdoc": "^0.14.2",
 		"@types/chai": "^4.3.5",
 		"@types/config": "^3.3.0",
@@ -65,4 +66,4 @@
 		"tsconfig-paths": "^4.2.0",
 		"typescript": "^5.0.4"
 	}
-}
+}

+ 162 - 0
backend/src/JobStatistics.spec.ts

@@ -0,0 +1,162 @@
+import { faker } from "@faker-js/faker";
+import {
+	JobStatistic,
+	JobStatistics,
+	JobStatisticsType
+} from "./JobStatistics";
+
+describe("JobStatistics", function () {
+	describe("getStats", function () {
+		it("should include jobs statistics", function () {
+			const statistics = new JobStatistics();
+
+			const jobName = faker.lorem.text();
+
+			statistics.updateStats(jobName, JobStatisticsType.TOTAL);
+
+			statistics.getStats()[jobName].total.should.be.equal(1);
+		});
+
+		[
+			JobStatisticsType.CONSTRUCTED,
+			JobStatisticsType.FAILED,
+			JobStatisticsType.QUEUED,
+			JobStatisticsType.SUCCESSFUL,
+			JobStatisticsType.TOTAL
+		].forEach(function (type) {
+			it(`should sum ${type} count for total`, function () {
+				const statistics = new JobStatistics();
+
+				statistics.updateStats(faker.lorem.text(), type);
+				statistics.updateStats(faker.lorem.text(), type);
+
+				statistics
+					.getStats()
+					.total[type as keyof JobStatistic].should.be.equal(2);
+			});
+		});
+
+		it(`should sum total duration for total`, function () {
+			const statistics = new JobStatistics();
+
+			const firstDuration = faker.number.int();
+			const secondDuration = faker.number.int();
+			const totalDuration = firstDuration + secondDuration;
+
+			statistics.updateStats(
+				faker.lorem.text(),
+				JobStatisticsType.DURATION,
+				firstDuration
+			);
+			statistics.updateStats(
+				faker.lorem.text(),
+				JobStatisticsType.DURATION,
+				secondDuration
+			);
+
+			statistics
+				.getStats()
+				.total.totalTime.should.be.equal(totalDuration);
+		});
+
+		it("should calculate average time for total", function () {
+			const statistics = new JobStatistics();
+
+			const firstJobName = faker.lorem.text();
+			const secondJobName = faker.lorem.text();
+			const firstDuration = faker.number.int();
+			const secondDuration = faker.number.int();
+			const averageDuration = (firstDuration + secondDuration) / 2;
+
+			statistics.updateStats(firstJobName, JobStatisticsType.TOTAL);
+			statistics.updateStats(
+				firstJobName,
+				JobStatisticsType.DURATION,
+				firstDuration
+			);
+
+			statistics.updateStats(secondJobName, JobStatisticsType.TOTAL);
+			statistics.updateStats(
+				secondJobName,
+				JobStatisticsType.DURATION,
+				secondDuration
+			);
+
+			statistics
+				.getStats()
+				.total.averageTime.should.be.equal(averageDuration);
+		});
+	});
+
+	describe("updateStats", function () {
+		const jobName = faker.lorem.text();
+
+		[
+			JobStatisticsType.CONSTRUCTED,
+			JobStatisticsType.FAILED,
+			JobStatisticsType.QUEUED,
+			JobStatisticsType.SUCCESSFUL,
+			JobStatisticsType.TOTAL
+		].forEach(function (type) {
+			it(`should increment ${type} count`, function () {
+				const statistics = new JobStatistics();
+
+				statistics.updateStats(jobName, type);
+				statistics.updateStats(jobName, type);
+
+				statistics
+					.getStats()
+					[jobName][type as keyof JobStatistic].should.be.equal(2);
+			});
+		});
+
+		it(`should add to total duration`, function () {
+			const statistics = new JobStatistics();
+
+			const firstDuration = faker.number.int();
+			const secondDuration = faker.number.int();
+			const totalDuration = firstDuration + secondDuration;
+
+			statistics.updateStats(
+				jobName,
+				JobStatisticsType.DURATION,
+				firstDuration
+			);
+			statistics.updateStats(
+				jobName,
+				JobStatisticsType.DURATION,
+				secondDuration
+			);
+
+			statistics
+				.getStats()
+				[jobName].totalTime.should.be.equal(totalDuration);
+		});
+
+		it("should calculate average time", function () {
+			const statistics = new JobStatistics();
+
+			const firstDuration = faker.number.int();
+			const secondDuration = faker.number.int();
+			const averageDuration = (firstDuration + secondDuration) / 2;
+
+			statistics.updateStats(jobName, JobStatisticsType.TOTAL);
+			statistics.updateStats(
+				jobName,
+				JobStatisticsType.DURATION,
+				firstDuration
+			);
+
+			statistics.updateStats(jobName, JobStatisticsType.TOTAL);
+			statistics.updateStats(
+				jobName,
+				JobStatisticsType.DURATION,
+				secondDuration
+			);
+
+			statistics
+				.getStats()
+				[jobName].averageTime.should.be.equal(averageDuration);
+		});
+	});
+});

+ 7 - 10
backend/src/JobStatistics.ts

@@ -7,16 +7,13 @@ export enum JobStatisticsType {
 	DURATION = "duration"
 }
 
+export type JobStatistic = Record<
+	Exclude<JobStatisticsType, "duration"> | "averageTime" | "totalTime",
+	number
+>;
+
 export class JobStatistics {
-	private _stats: Record<
-		string,
-		Record<
-			| Exclude<JobStatisticsType, "duration">
-			| "averageTime"
-			| "totalTime",
-			number
-		>
-	>;
+	private _stats: Record<string, JobStatistic>;
 
 	public constructor() {
 		this._stats = {};
@@ -27,7 +24,7 @@ export class JobStatistics {
 	 *
 	 * @returns Job queue statistics
 	 */
-	public getStats() {
+	public getStats(): Record<string | "total", JobStatistic> {
 		const total = Object.values(this._stats).reduce(
 			(a, b) => ({
 				successful: a.successful + b.successful,