Skip to content

Commit

Permalink
adding api tests
Browse files Browse the repository at this point in the history
  • Loading branch information
JeromeBu committed Jun 7, 2024
1 parent 1bd52d5 commit 1d6e3da
Show file tree
Hide file tree
Showing 10 changed files with 309 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { DbApi, Db } from "../ports/DbApi";
import { gitSsh } from "../../tools/gitSsh";
import type { DbApi, Db } from "../../ports/DbApi";
import { gitSsh } from "../../../tools/gitSsh";
import { Deferred } from "evt/tools/Deferred";
import { type CompiledData, compiledDataPrivateToPublic } from "../ports/CompileData";
import { type CompiledData, compiledDataPrivateToPublic } from "../../ports/CompileData";
import * as fs from "fs";
import { join as pathJoin } from "path";
import type { ReturnType } from "tsafe";
Expand All @@ -24,7 +24,7 @@ export type GitDbApiParams = {
sshPrivateKey: string;
};

export function createGitDbApi(params: GitDbApiParams): { dbApi: DbApi; initializeDbApiCache: () => Promise<void> } {
export function createGitDbApi(params: GitDbApiParams): Db.DbApiAndInitializeCache {
const { dataRepoSshUrl, sshPrivateKeyName, sshPrivateKey } = params;

const dbApi: DbApi = {
Expand Down
61 changes: 61 additions & 0 deletions api/src/core/adapters/dbApi/createInMemoryDbApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { CompiledData } from "../../ports/CompileData";
import type { Db, DbApi } from "../../ports/DbApi";

export class InMemoryDbApi implements DbApi {
#softwareRows: Db.SoftwareRow[] = [];
#agentRows: Db.AgentRow[] = [];
#softwareReferentRows: Db.SoftwareReferentRow[] = [];
#softwareUserRows: Db.SoftwareUserRow[] = [];
#instanceRows: Db.InstanceRow[] = [];

#compiledData: CompiledData<"private"> = [];

async fetchDb() {
console.log("fetchDb");
return {
softwareRows: this.#softwareRows,
agentRows: this.#agentRows,
softwareReferentRows: this.#softwareReferentRows,
softwareUserRows: this.#softwareUserRows,
instanceRows: this.#instanceRows
};
}

async updateDb({ newDb }: { newDb: Db }) {
this.#softwareRows = newDb.softwareRows;
this.#agentRows = newDb.agentRows;
this.#softwareReferentRows = newDb.softwareReferentRows;
this.#softwareUserRows = newDb.softwareUserRows;
this.#instanceRows = newDb.instanceRows;
}

async fetchCompiledData() {
return this.#compiledData;
}

async updateCompiledData({ newCompiledData }: { newCompiledData: CompiledData<"private"> }) {
this.#compiledData = newCompiledData;
}

// test helpers

get softwareRows() {
return this.#softwareRows;
}

get agentRows() {
return this.#agentRows;
}

get softwareReferentRows() {
return this.#softwareReferentRows;
}

get softwareUserRows() {
return this.#softwareUserRows;
}

get instanceRows() {
return this.#instanceRows;
}
}
28 changes: 23 additions & 5 deletions api/src/core/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { createCore, createObjectThatThrowsIfAccessed, type GenericCore } from "redux-clean-architecture";
import { createCompileData } from "./adapters/compileData";
import { comptoirDuLibreApi } from "./adapters/comptoirDuLibreApi";
import { createGitDbApi, type GitDbApiParams } from "./adapters/dbApi";
import { createGitDbApi, type GitDbApiParams } from "./adapters/dbApi/createGitDbApi";
import { InMemoryDbApi } from "./adapters/dbApi/createInMemoryDbApi";
import { getCnllPrestatairesSill } from "./adapters/getCnllPrestatairesSill";
import { getServiceProviders } from "./adapters/getServiceProviders";
import { createGetSoftwareLatestVersion } from "./adapters/getSoftwareLatestVersion";
Expand All @@ -12,15 +13,20 @@ import { getHalSoftwareOptions } from "./adapters/hal/getHalSoftwareOptions";
import { createKeycloakUserApi, type KeycloakUserApiParams } from "./adapters/userApi";
import type { CompileData } from "./ports/CompileData";
import type { ComptoirDuLibreApi } from "./ports/ComptoirDuLibreApi";
import type { DbApi } from "./ports/DbApi";
import { DbApi, Db } from "./ports/DbApi";
import type { ExternalDataOrigin, GetSoftwareExternalData } from "./ports/GetSoftwareExternalData";
import type { GetSoftwareExternalDataOptions } from "./ports/GetSoftwareExternalDataOptions";
import type { GetSoftwareLatestVersion } from "./ports/GetSoftwareLatestVersion";
import type { UserApi } from "./ports/UserApi";
import { usecases } from "./usecases";

type GitDbConfig = { dbKind: "git" } & GitDbApiParams;
type InMemoryDbConfig = { dbKind: "inMemory" };

type DbConfig = GitDbConfig | InMemoryDbConfig;

type ParamsOfBootstrapCore = {
gitDbApiParams: GitDbApiParams;
dbConfig: DbConfig;
keycloakUserApiParams: KeycloakUserApiParams | undefined;
githubPersonalAccessTokenForApiRateLimit: string;
doPerPerformPeriodicalCompilation: boolean;
Expand All @@ -45,9 +51,21 @@ export type State = Core["types"]["State"];
export type Thunks = Core["types"]["Thunks"];
export type CreateEvt = Core["types"]["CreateEvt"];

const getDbApiAndInitializeCache = (dbConfig: DbConfig): Db.DbApiAndInitializeCache => {
console.log("dbConfig", dbConfig);
if (dbConfig.dbKind === "git") return createGitDbApi(dbConfig);
if (dbConfig.dbKind === "inMemory")
return {
dbApi: new InMemoryDbApi(),
initializeDbApiCache: async () => {}
};
const shouldNotBeReached: never = dbConfig;
throw new Error(`Unsupported case: ${shouldNotBeReached}`);
};

export async function bootstrapCore(params: ParamsOfBootstrapCore): Promise<{ core: Core; context: Context }> {
const {
gitDbApiParams,
dbConfig,
keycloakUserApiParams,
githubPersonalAccessTokenForApiRateLimit,
doPerPerformPeriodicalCompilation,
Expand All @@ -70,7 +88,7 @@ export async function bootstrapCore(params: ParamsOfBootstrapCore): Promise<{ co
getServiceProviders
});

const { dbApi, initializeDbApiCache } = createGitDbApi(gitDbApiParams);
const { dbApi, initializeDbApiCache } = getDbApiAndInitializeCache(dbConfig);

const { userApi, initializeUserApiCache } =
keycloakUserApiParams === undefined
Expand Down
2 changes: 2 additions & 0 deletions api/src/core/ports/DbApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ export namespace Db {
referencedSinceTime: number;
updateTime: number;
};

export type DbApiAndInitializeCache = { dbApi: DbApi; initializeDbApiCache: () => Promise<void> };
}

export type Os = "windows" | "linux" | "mac" | "android" | "ios";
Expand Down
5 changes: 5 additions & 0 deletions api/src/core/usecases/readWriteSillData/thunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,15 @@ export const thunks = {
privateThunks.transaction(async newDb => {
const { softwareRows, agentRows } = newDb;

console.log("before assert");
assert(
softwareRows.find(s => {
const t = (name: string) => name.toLowerCase().replace(/ /g, "-");
return t(s.name) === t(formData.softwareName);
}) === undefined,
"There is already a software with this name"
);
console.log("after assert");

const softwareId =
newDb.softwareRows.map(({ id }) => id).reduce((prev, curr) => Math.max(prev, curr), 0) + 1;
Expand Down Expand Up @@ -164,6 +166,7 @@ export const thunks = {
});
}

console.log("new db ok");
return {
newDb,
"commitMessage": `Add software: ${formData.softwareName}`
Expand Down Expand Up @@ -601,6 +604,8 @@ export const thunks = {
})
);

console.log("----- about to call userApi.updateUserEmail -----");

await userApi.updateUserEmail({
"email": newEmail,
userId
Expand Down
49 changes: 49 additions & 0 deletions api/src/rpc/createTestCaller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { bootstrapCore } from "../core";
import { InMemoryDbApi } from "../core/adapters/dbApi/createInMemoryDbApi";
import { ExternalDataOrigin } from "../core/ports/GetSoftwareExternalData";
import { createRouter } from "./router";
import { User } from "./user";

type TestCallerConfig = {
user: User | undefined;
};

export const defaultUser: User = {
id: "1",
email: "[email protected]",
organization: "Default Organization"
};

export type ApiCaller = Awaited<ReturnType<typeof createTestCaller>>["apiCaller"];

export const createTestCaller = async ({ user }: TestCallerConfig = { user: defaultUser }) => {
const externalSoftwareDataOrigin: ExternalDataOrigin = "wikidata";

const { core, context } = await bootstrapCore({
"dbConfig": { dbKind: "inMemory" },
"keycloakUserApiParams": undefined,
"githubPersonalAccessTokenForApiRateLimit": "fake-token",
"doPerPerformPeriodicalCompilation": false,
"doPerformCacheInitialization": false,
"externalSoftwareDataOrigin": externalSoftwareDataOrigin
});

const jwtClaimByUserKey = {
"id": "sub",
"email": "email",
"organization": "organization"
};

const { router } = createRouter({
core,
coreContext: context,
keycloakParams: undefined,
redirectUrl: undefined,
externalSoftwareDataOrigin,
readmeUrl: "http://readme.url",
termsOfServiceUrl: "http://terms.url",
jwtClaimByUserKey
});

return { apiCaller: router.createCaller({ user }), inMemoryDb: context.dbApi as InMemoryDbApi };
};
13 changes: 4 additions & 9 deletions api/src/rpc/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,7 @@ import { assert } from "tsafe/assert";
import { z } from "zod";
import type { Context as CoreContext, Core } from "../core";
import { ExternalDataOrigin, Language, languages, type LocalizedString } from "../core/ports/GetSoftwareExternalData";
import type {
DeclarationFormData,
InstanceFormData,
Os,
SoftwareFormData,
SoftwareType
} from "../core/usecases/readWriteSillData";
import type { InstanceFormData, Os, SoftwareFormData, SoftwareType } from "../core/usecases/readWriteSillData";
import type { KeycloakParams } from "../tools/createValidateKeycloakSignature";
import { getMonorepoRootPackageJson } from "../tools/getMonorepoRootPackageJson";
import type { OptionalIfCanBeUndefined } from "../tools/OptionalIfCanBeUndefined";
Expand Down Expand Up @@ -373,8 +367,8 @@ export function createRouter(params: {

return { agent };
}),
"getAllowedEmailRegexp": loggedProcedure.query(coreContext.userApi.getAllowedEmailRegexp),
"getAllOrganizations": loggedProcedure.query(coreContext.userApi.getAllOrganizations),
"getAllowedEmailRegexp": loggedProcedure.query(() => coreContext.userApi.getAllowedEmailRegexp()),
"getAllOrganizations": loggedProcedure.query(() => coreContext.userApi.getAllOrganizations()),
"changeAgentOrganization": loggedProcedure
.input(
z.object({
Expand Down Expand Up @@ -566,6 +560,7 @@ const zSoftwareFormData = (() => {
return zOut as z.ZodType<SoftwareFormData>;
})();

export type DeclarationFormData = z.infer<typeof zDeclarationFormData>;
const zDeclarationFormData = (() => {
const zUser = z.object({
"declarationType": z.literal("user"),
Expand Down
110 changes: 105 additions & 5 deletions api/src/rpc/routes.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,110 @@
import { expectToEqual } from "../tools/test.helpers";
import { describe, it } from "vitest";
import { describe, expect, it, beforeAll } from "vitest";
import { InMemoryDbApi } from "../core/adapters/dbApi/createInMemoryDbApi";
import { CompiledData } from "../core/ports/CompileData";
import {
createDeclarationFormData,
createSoftwareFormData,
expectToEqual,
expectToMatchObject
} from "../tools/test.helpers";
import { ApiCaller, createTestCaller, defaultUser } from "./createTestCaller";

const softwareFormData = createSoftwareFormData();
const declarationFormData = createDeclarationFormData();

describe("RPC e2e tests", () => {
describe("route something", () => {
it("works", () => {
expectToEqual({ not: "bad" }, { not: "good" });
let apiCaller: ApiCaller;
let inMemoryDb: InMemoryDbApi;

describe("Add new agent as user - Wrong paths", () => {
it("fails with UNAUTHORIZED if user is not logged in", async () => {
({ apiCaller, inMemoryDb } = await createTestCaller({ user: undefined }));
expect(
apiCaller.createUserOrReferent({
formData: declarationFormData,
softwareName: "Some software"
})
).rejects.toThrow("UNAUTHORIZED");
});

it("fails when software is not found in SILL", async () => {
({ apiCaller, inMemoryDb } = await createTestCaller());
expect(
apiCaller.createUserOrReferent({
formData: declarationFormData,
softwareName: "Some software"
})
).rejects.toThrow("Software not in SILL");
});
});

describe("Scenario - Add a new software then mark an agent as user of this software", () => {
beforeAll(async () => {
({ apiCaller, inMemoryDb } = await createTestCaller());
});

it("adds a new software", async () => {
expect(inMemoryDb.softwareRows).toHaveLength(0);
const initialSoftwares = await apiCaller.getSoftwares();
expectToEqual(initialSoftwares, []);

await apiCaller.createSoftware({
formData: softwareFormData
});
expect(inMemoryDb.softwareRows).toHaveLength(1);
const expectedSoftware: Partial<CompiledData.Software<"public">> = {
"description": softwareFormData.softwareDescription,
"externalId": softwareFormData.externalId,
"doRespectRgaa": softwareFormData.doRespectRgaa,
"isFromFrenchPublicService": softwareFormData.isFromFrenchPublicService,
"isPresentInSupportContract": softwareFormData.isPresentInSupportContract,
"keywords": softwareFormData.softwareKeywords,
"license": softwareFormData.softwareLicense,
"logoUrl": softwareFormData.softwareLogoUrl,
"name": softwareFormData.softwareName,
"softwareType": softwareFormData.softwareType,
"versionMin": softwareFormData.softwareMinimalVersion,
"testUrls": [],
"workshopUrls": [],
"categories": [],
"isStillInObservation": false
};

expectToMatchObject(inMemoryDb.softwareRows[0], {
...expectedSoftware,
"addedByAgentEmail": defaultUser.email,
"similarSoftwareExternalDataIds": softwareFormData.similarSoftwareExternalDataIds
});
});

it("gets the new software in the list", async () => {
const softwares = await apiCaller.getSoftwares();
expect(softwares).toHaveLength(1);
expectToMatchObject(softwares[0], { softwareName: softwareFormData.softwareName });
});

it("adds an agent as user of the software", async () => {
expect(inMemoryDb.agentRows).toHaveLength(1);
expect(inMemoryDb.softwareRows).toHaveLength(1);
expect(inMemoryDb.softwareUserRows).toHaveLength(0);
await apiCaller.createUserOrReferent({
formData: declarationFormData,
softwareName: "Some software"
});

if (declarationFormData.declarationType !== "user")
throw new Error("This test is only for user declaration");

expect(inMemoryDb.softwareUserRows).toHaveLength(1);

expectToEqual(inMemoryDb.softwareUserRows[0], {
"agentEmail": defaultUser.email,
"softwareId": inMemoryDb.softwareRows[0].id,
"os": declarationFormData.os,
"serviceUrl": declarationFormData.serviceUrl,
"useCaseDescription": declarationFormData.usecaseDescription,
"version": declarationFormData.version
});
});
});
});
3 changes: 2 additions & 1 deletion api/src/rpc/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ export async function startRpcService(params: {
console.log({ isDevEnvironnement });

const { core, context: coreContext } = await bootstrapCore({
"gitDbApiParams": {
"dbConfig": {
"dbKind": "git",
dataRepoSshUrl,
"sshPrivateKeyName": sshPrivateKeyForGitName,
"sshPrivateKey": sshPrivateKeyForGit
Expand Down
Loading

0 comments on commit 1d6e3da

Please sign in to comment.