Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): #3121 scdl integration script #3124

Merged
merged 25 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2f75c13
feat(api): #3121 scdl integration script
461OceanBd Jan 15, 2025
3944c13
feat(api): #3121 scdl integration script
461OceanBd Jan 16, 2025
45ac075
feat(api): #3121 scdl integration script
461OceanBd Jan 16, 2025
068ab91
feat(api): #3121 scdl integration script
461OceanBd Jan 16, 2025
744fb66
feat(api): #3121 scdl integration script
461OceanBd Jan 16, 2025
208be39
feat(api): #3121 scdl integration script
461OceanBd Jan 16, 2025
d54200c
feat(api): #3121 scdl integration script
461OceanBd Jan 28, 2025
9581c11
feat(api): #3121 scdl integration script
461OceanBd Jan 28, 2025
304b155
feat(api): #3121 scdl integration script
461OceanBd Jan 28, 2025
e22f88e
feat(api): #3121 scdl integration script
461OceanBd Jan 28, 2025
2387084
feat(api): #3121 scdl integration script
461OceanBd Jan 28, 2025
ecd6dfe
feat(api): #3121 scdl integration script
461OceanBd Jan 28, 2025
11101b0
feat(api): #3121 scdl integration script
461OceanBd Jan 28, 2025
4ffaf5a
feat(api): #3121 scdl integration script
461OceanBd Jan 28, 2025
cf533e0
feat(api): #3121 scdl integration script
461OceanBd Jan 29, 2025
33ab374
feat(api): #3121 scdl integration script
461OceanBd Jan 29, 2025
6e9e94b
feat(api): #3121 scdl integration script
461OceanBd Jan 29, 2025
d9baa46
feat(api): #3121 scdl integration script
461OceanBd Jan 29, 2025
eee623b
feat(api): #3121 scdl integration script
461OceanBd Jan 29, 2025
8b4adb8
feat(api): #3121 scdl integration script
461OceanBd Jan 29, 2025
841bc3f
feat(api): #3121 scdl integration script
461OceanBd Jan 29, 2025
e1c2b95
feat(api): #3121 scdl integration script
461OceanBd Jan 30, 2025
ac85d10
Merge branch 'develop' into 3121-scdl-integration-script
alice-telescoop Feb 3, 2025
9788a96
Merge branch 'develop' into 3121-scdl-integration-script
alice-telescoop Feb 3, 2025
729b6e2
Merge branch 'develop' into 3121-scdl-integration-script
alice-telescoop Feb 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/api/src/@enums/FileExtensionEnum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum FileExtensionEnum {
CSV = "csv",
XLS = "xls",
XLSX = "xlsx",
}
15 changes: 15 additions & 0 deletions packages/api/src/@types/ScdlDataIntegration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
type BaseScdlParseArgs = { producerSlug: string; exportDate: string };
export type ScdlParseArgs = BaseScdlParseArgs & { delimiter?: string; quote?: string };
export type ScdlParseXlsArgs = BaseScdlParseArgs & { pageName?: string; rowOffset?: number | string };

export interface ScdlFileProcessingConfig {
name: string;
parseParams: ScdlParseArgs | ScdlParseXlsArgs;
addProducer: boolean;
producerName?: string;
producerSiret?: string;
}

export interface ScdlFileProcessingConfigList {
files: ScdlFileProcessingConfig[];
}
2 changes: 2 additions & 0 deletions packages/api/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { initIndexes } from "./shared/MongoInit";
import GeoCli from "./interfaces/cli/Geo.cli";
import DataBretagneCli from "./interfaces/cli/DataBretagne.cli";
import PaymentFlatCli from "./interfaces/cli/PaymentFlat.cli";
import ScdlBatchCli from "./interfaces/cli/ScdlBatch.cli";
async function main() {
await connectDB();
await initIndexes();
Expand Down Expand Up @@ -52,6 +53,7 @@ async function main() {
GeoCli,
DataBretagneCli,
PaymentFlatCli,
ScdlBatchCli,
];

const args = process.argv.slice(2);
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/configurations/scdlIntegration.conf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const SCDL_FILE_PROCESSING_PATH = process.env.SCDL_FILE_PROCESSING_PATH || "./data-integration";
export const SCDL_FILE_PROCESSING_CONFIG_FILENAME = "scdl-file-processing.config.json";
6 changes: 3 additions & 3 deletions packages/api/src/interfaces/cli/Scdl.cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ export default class ScdlCli {
producerSlug: string,
exportDate: string,
pageName?: string,
rowOffsetStr: number | string = 0,
rowOffset: number | string = 0,
) {
await this.validateGenericInput(file, producerSlug, exportDate);
const rowOffset = typeof rowOffsetStr === "number" ? rowOffsetStr : parseInt(rowOffsetStr);
const parsedRowOffset = typeof rowOffset === "number" ? rowOffset : parseInt(rowOffset);
const fileContent = fs.readFileSync(file);
const { entities, errors } = ScdlGrantParser.parseExcel(fileContent, pageName, rowOffset);
const { entities, errors } = ScdlGrantParser.parseExcel(fileContent, pageName, parsedRowOffset);
Comment on lines -35 to +40
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ils étaient pas bien les noms de variables ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'était pour répondre à la remarque de Maxime sur le fait que c'est bizarre que la variable rowOffsetStr s'appelle comme ça alors qu'on peut aussi mettre un number

await Promise.all([
this.persistEntities(entities, producerSlug, exportDate as string),
this.exportErrors(errors, file),
Expand Down
295 changes: 295 additions & 0 deletions packages/api/src/interfaces/cli/ScdlBatch.cli.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
import path from "path";
import ScdlBatchCli from "./ScdlBatch.cli";
import fs from "fs";

import {
SCDL_FILE_PROCESSING_PATH,
SCDL_FILE_PROCESSING_CONFIG_FILENAME,
} from "../../configurations/scdlIntegration.conf";
import ScdlCli from "../cli/Scdl.cli";
import { ScdlFileProcessingConfigList, ScdlParseArgs, ScdlParseXlsArgs } from "../../@types/ScdlDataIntegration";

jest.mock("../../configurations/scdlIntegration.conf", () => ({
SCDL_FILE_PROCESSING_PATH: path.resolve(__dirname, "test-integration"),
SCDL_FILE_PROCESSING_CONFIG_FILENAME: "test-scdl-file-processing.config.json",
}));
jest.mock("../cli/Scdl.cli");
jest.mock("fs");

const validConfigData: ScdlFileProcessingConfigList = {
files: [
{
name: "donnees-a-integrer1.csv",
parseParams: { producerSlug: "producerSlug1", exportDate: "2025-01-13" },
addProducer: true,
producerName: "Test Producer 1",
producerSiret: "12345678901",
},
{
name: "donnees-a-integrer2.csv",
parseParams: { producerSlug: "producerSlug2", exportDate: "2025-01-14" },
addProducer: true,
producerName: "Test Producer 2",
producerSiret: "12345678902",
},
{
name: "donnees-a-integrer3.csv",
parseParams: { producerSlug: "producerSlug2", exportDate: "2025-01-15" },
addProducer: false,
},
],
};

describe("scdl data integration script", () => {
let scdlBatchCli: ScdlBatchCli;
const testFilePath = path.join(SCDL_FILE_PROCESSING_PATH, SCDL_FILE_PROCESSING_CONFIG_FILENAME);

let addProducerMock: jest.SpyInstance<Promise<void>, [slug: string, name: string, siret: string]>;
let parseMock: jest.SpyInstance<
Promise<void>,
[
file: string,
producerSlug: string,
exportDate: string,
delimiter?: string | undefined,
quote?: string | undefined,
]
>;
let parseXlsMock: jest.SpyInstance<
Promise<void>,
[
file: string,
producerSlug: string,
exportDate: string,
pageName?: string | undefined,
rowOffset?: string | number | undefined,
]
>;

beforeAll(() => {
scdlBatchCli = new ScdlBatchCli();
});

beforeEach(() => {
jest.spyOn(process, "exit").mockImplementation((() => {}) as (code?: any) => never);
addProducerMock = jest.spyOn(ScdlCli.prototype, "addProducer").mockResolvedValue();
parseMock = jest.spyOn(ScdlCli.prototype, "parse").mockResolvedValue();
parseXlsMock = jest.spyOn(ScdlCli.prototype, "parseXls").mockResolvedValue();
});

describe("loadConfig method", () => {
it("should load config file successfully", () => {
jest.spyOn(fs, "readFileSync").mockReturnValueOnce(JSON.stringify(validConfigData));
// @ts-expect-error: protected
const result = scdlBatchCli.loadConfig();

expect(result).toEqual(validConfigData);
});

it("should throw an error when the JSON file is invalid", () => {
jest.spyOn(fs, "readFileSync").mockImplementationOnce(() => {
throw new Error("Unexpected token i in JSON at position 2");
});
// @ts-expect-error: protected
expect(() => scdlBatchCli.loadConfig()).toThrowError(new Error("Unexpected token i in JSON at position 2"));
});
});

describe("Test for processFile method", () => {
const fileConfig = {
name: "donnees-a-integrer1.csv",
parseParams: { producerSlug: "producerSlug1", exportDate: "2025-01-13" } as ScdlParseArgs,
addProducer: true,
producerName: "Test Producer 1",
producerSiret: "12345678901",
};

const fileConfigWrongType = {
name: "donnees-a-integrer1.doc",
parseParams: { producerSlug: "producerSlug1", exportDate: "2025-01-13" } as ScdlParseXlsArgs,
addProducer: true,
producerName: "Test Producer 1",
producerSiret: "12345678901",
};

it("should process file methods correctly with addProducer called with correct params", async () => {
// @ts-expect-error: protected
await expect(scdlBatchCli.processFile(fileConfig)).resolves.toBeUndefined();

expect(addProducerMock).toHaveBeenCalledWith(
fileConfig.parseParams.producerSlug,
fileConfig.producerName,
fileConfig.producerSiret,
);
});

it("should process file methods correctly with parse method called with correct params", async () => {
// @ts-expect-error: protected
await expect(scdlBatchCli.processFile(fileConfig)).resolves.toBeUndefined();

expect(parseMock).toHaveBeenCalledWith(
expect.stringContaining(fileConfig.name),
fileConfig.parseParams.producerSlug,
fileConfig.parseParams.exportDate,
undefined,
undefined,
);
});

it("should process file methods correctly with parseXls method not to have been called", async () => {
// @ts-expect-error: protected
await expect(scdlBatchCli.processFile(fileConfig)).resolves.toBeUndefined();
expect(parseXlsMock).not.toHaveBeenCalled();
});

it("should catch error", async () => {
addProducerMock = jest
.spyOn(ScdlCli.prototype, "addProducer")
.mockRejectedValue(new Error("Mocked addProducer error"));
// @ts-expect-error: protected
await expect(scdlBatchCli.processFile(fileConfig)).resolves.toBeUndefined();
});

it("should not call parse method when addProducer error", async () => {
addProducerMock = jest
.spyOn(ScdlCli.prototype, "addProducer")
.mockRejectedValue(new Error("Mocked addProducer error"));
// @ts-expect-error: protected
await expect(scdlBatchCli.processFile(fileConfig)).resolves.toBeUndefined();
expect(parseMock).not.toHaveBeenCalled();
});

it("should catch Unsupported file type error", async () => {
// @ts-expect-error: protected
await expect(scdlBatchCli.processFile(fileConfigWrongType)).resolves.toBeUndefined();
});

it("should not call parse method when wrong file type", async () => {
// @ts-expect-error: protected
await expect(scdlBatchCli.processFile(fileConfigWrongType)).resolves.toBeUndefined();
expect(parseMock).not.toHaveBeenCalled();
});

it("should not call parseXls method when wrong file type", async () => {
// @ts-expect-error: protected
await expect(scdlBatchCli.processFile(fileConfigWrongType)).resolves.toBeUndefined();
expect(parseXlsMock).not.toHaveBeenCalled();
});

it("should catch Producer already exists error", async () => {
addProducerMock = jest
.spyOn(ScdlCli.prototype, "addProducer")
.mockRejectedValue(new Error("Producer already exists"));
// @ts-expect-error: protected
await expect(scdlBatchCli.processFile(fileConfig)).resolves.toBeUndefined();
});

it("should call parse method when producer already exists", async () => {
addProducerMock = jest
.spyOn(ScdlCli.prototype, "addProducer")
.mockRejectedValue(new Error("Producer already exists"));
// @ts-expect-error: protected
await expect(scdlBatchCli.processFile(fileConfig)).resolves.toBeUndefined();
expect(parseMock).toHaveBeenCalledTimes(1);
});
});

describe("main", () => {
it("should call ScdlCli methods with correct arguments", async () => {
jest.spyOn(fs, "readFileSync").mockReturnValueOnce(JSON.stringify(validConfigData));

await expect(scdlBatchCli.import()).resolves.toBeUndefined();

expect(addProducerMock).toHaveBeenCalledTimes(2);
expect(addProducerMock).toHaveBeenCalledWith("producerSlug1", "Test Producer 1", "12345678901");
expect(addProducerMock).toHaveBeenCalledWith("producerSlug2", "Test Producer 2", "12345678902");

expect(parseMock).toHaveBeenCalledTimes(3);
expect(parseMock).toHaveBeenCalledWith(
path.join(SCDL_FILE_PROCESSING_PATH, "donnees-a-integrer1.csv"),
"producerSlug1",
"2025-01-13",
undefined,
undefined,
);
expect(parseMock).toHaveBeenCalledWith(
path.join(SCDL_FILE_PROCESSING_PATH, "donnees-a-integrer2.csv"),
"producerSlug2",
"2025-01-14",
undefined,
undefined,
);
expect(parseMock).toHaveBeenCalledWith(
path.join(SCDL_FILE_PROCESSING_PATH, "donnees-a-integrer3.csv"),
"producerSlug2",
"2025-01-15",
undefined,
undefined,
);
expect(parseXlsMock).not.toHaveBeenCalled();
});

it("should throw Unexpected token error", async () => {
jest.spyOn(fs, "readFileSync").mockImplementationOnce(() => {
throw new Error("Unexpected token i in JSON at position 2");
});
await expect(scdlBatchCli.import()).rejects.toThrow("Unexpected token i in JSON at position 2");
});

it("should throw Invalid configuration file error", async () => {
const invalidConfigData = {
files: [
{
name: "donnees-a-integrer1.csv",
addProducer: true,
producerName: "Test Producer 1",
producerSiret: "12345678901",
},
],
};
jest.spyOn(fs, "readFileSync").mockReturnValueOnce(JSON.stringify(invalidConfigData));
fs.writeFileSync(testFilePath, JSON.stringify(invalidConfigData));

await expect(scdlBatchCli.import()).rejects.toThrow(
"Invalid configuration file: The config does not match the expected structure.",
);
});

it("when error with one file, should process continue for other files", async () => {
const configData = {
files: [
{
name: "donnees-a-integrer1.xlsx",
parseParams: { producerSlug: "producerSlug1", exportDate: "2025-01-13" } as ScdlParseXlsArgs,
addProducer: true,
producerName: "Test Producer 1",
producerSiret: "12345678901",
},
{
name: "donnees-a-integrer2.csv",
parseParams: { producerSlug: "producerSlug2", exportDate: "2025-01-14" } as ScdlParseArgs,
addProducer: true,
producerName: "Test Producer 2",
producerSiret: "12345678902",
},
{
name: "donnees-a-integrer3.csv",
parseParams: { producerSlug: "producerSlug3", exportDate: "2025-01-15" } as ScdlParseArgs,
addProducer: true,
producerName: "Test Producer 3",
producerSiret: "12345678903",
},
],
};
jest.spyOn(fs, "readFileSync").mockReturnValueOnce(JSON.stringify(configData));
parseXlsMock = jest
.spyOn(ScdlCli.prototype, "parseXls")
.mockRejectedValue(new Error("Mocked addProducer error"));

await expect(scdlBatchCli.import()).resolves.toBeUndefined();
expect(addProducerMock).toHaveBeenCalledTimes(3);
expect(parseMock).toHaveBeenCalledTimes(2);
expect(parseXlsMock).toHaveBeenCalledTimes(1);
});
});
});
Loading