feat(caprover): allow to trigger an application update

+ added test coverage
This commit is contained in:
Pierre Martin
2024-03-10 09:49:52 +01:00
parent df5bfe4e1a
commit fd3783cc8d
18 changed files with 650 additions and 73 deletions

43
domain/AppQueries.test.ts Normal file
View File

@@ -0,0 +1,43 @@
import { beforeEach, describe, expect, it } from "bun:test";
import { ApplicationUpdateStarted } from "./events/ApplicationUpdateStarted";
import { TestApp } from "./testing/TestApp";
describe("AppQueries", () => {
describe("pendingApplicationUpdates", () => {
let app: TestApp;
beforeEach(() => {
app = new TestApp([
new ApplicationUpdateStarted(
{ id: "mail", newVersion: "1.0.0" },
new Date()
),
new ApplicationUpdateStarted(
{ id: "blog", newVersion: "10.0.1" },
new Date()
),
]);
});
it("should return an empty array when there are no pending updates", () => {
const app = new TestApp();
expect(app.queries.pendingApplicationUpdates()).toEqual([]);
});
it("should return all pending updates when no appName is provided", () => {
expect(app.queries.pendingApplicationUpdates()).toEqual([
{ id: "mail", newVersion: "1.0.0" },
{ id: "blog", newVersion: "10.0.1" },
]);
});
it("should return all pending updates for the provided application", () => {
expect(app.queries.pendingApplicationUpdates("mail")).toEqual([
{ id: "mail", newVersion: "1.0.0" },
]);
});
it("should return an empty array when there are no pending updates for the provided appName", () => {
expect(app.queries.pendingApplicationUpdates("unknown")).toEqual([]);
});
});
});

View File

@@ -5,7 +5,10 @@ export default class AppQueries {
constructor(private readonly projections: AppProjections) {}
pendingApplicationUpdates(appName?: string): UpdateDefinition[] {
// TODO: Implement filtering by appName
return this.projections.ApplicationUpdates.getPendingUpdates();
const updates = this.projections.ApplicationUpdates.getPendingUpdates();
if (!appName) {
return updates;
}
return updates.filter((update) => update.id === appName);
}
}

View File

@@ -7,7 +7,6 @@ export default class ApplicationUpdates implements DomainProjection {
private readonly pendingUpdates: UpdateDefinition[] = [];
handle(event: DomainEvent<any>): void {
if (event.type === "ApplicationUpdateStarted") {
console.log("ApplicationUpdateStarted", event.payload);
this.pendingUpdates.push(event.payload);
}
}

44
domain/testing/TestApp.ts Normal file
View File

@@ -0,0 +1,44 @@
import AppProjections from "../AppProjections";
import AppQueries from "../AppQueries";
import EventStore from "../EventStore";
export class TestApp {
readonly eventStore: InMemoryEventStore = new InMemoryEventStore();
readonly projections: AppProjections = new AppProjections();
readonly queries: AppQueries = new AppQueries(this.projections);
constructor(history: DomainEvent<any>[] = []) {
this.projections.getAll().forEach((projection) => {
this.eventStore.subscribe(projection);
});
for (const event of history) {
this.eventStore.append(event);
}
}
}
class InMemoryEventStore implements EventStore {
private handlers: DomainProjection[] = [];
private readonly events: DomainEvent<any>[] = [];
async append(event: DomainEvent<any>): Promise<void> {
this.events.push(event);
for (const handler of this.handlers) {
handler.handle(event);
}
}
subscribe(projection: DomainProjection): void {
this.handlers.push(projection);
}
async replay(): Promise<void> {
throw new Error(
"Replay is not relevant for InMemoryEventStore. Use append instead."
);
}
getAllEvents(): DomainEvent<any>[] {
return this.events;
}
}