Skip to content

Commit

Permalink
Merge branch 'development' into chore/test-apply-to-all-use-case
Browse files Browse the repository at this point in the history
  • Loading branch information
delemaf committed Jun 13, 2024
2 parents 0744010 + b70fd1c commit 787dcf1
Show file tree
Hide file tree
Showing 23 changed files with 765 additions and 16 deletions.
38 changes: 38 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Application testing
on:
push:
workflow_dispatch:
jobs:
unit-tests:
name: Unit tests
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Setup Node
uses: actions/setup-node@v1
with:
node-version: "16.14.0"

- name: Install yarn
run: npm install -g yarn

- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"

- name: Cache yarn dependencies
uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install --frozen-lockfile --silent

- name: Run jest tests
run: yarn test
4 changes: 2 additions & 2 deletions src/data/common/Dhis2DataElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { Dhis2DataStoreDataForm } from "./Dhis2DataStoreDataForm";
export class Dhis2DataElement {
constructor(private api: D2Api) {}

async get(ids: Id[]): Promise<Record<Id, DataElement>> {
const config = await Dhis2DataStoreDataForm.build(this.api);
async get(ids: Id[], dataSetCode: string): Promise<Record<Id, DataElement>> {
const config = await Dhis2DataStoreDataForm.build(this.api, dataSetCode);
const idGroups = _(ids).uniq().chunk(100).value();

const resList = await promiseMap(idGroups, idsGroup =>
Expand Down
2 changes: 1 addition & 1 deletion src/data/common/Dhis2DataFormRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class Dhis2DataFormRepository implements DataFormRepository {
.map(dataElement => ({ id: dataElement.id, code: dataElement.code }))
.value();

const dataElements = await new Dhis2DataElement(this.api).get(dataElementIds);
const dataElements = await new Dhis2DataElement(this.api).get(dataElementIds, dataSet.code);

const dataElementsRulesConfig = this.buildRulesFromConfig(dataElements, configDataForm, allDataElements);
return dataSet.sections.map((section): Section => {
Expand Down
17 changes: 10 additions & 7 deletions src/data/common/Dhis2DataStoreDataForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,16 @@ export class Dhis2DataStoreDataForm {
}

static async build(api: D2Api, dataSetCode?: string): Promise<Dhis2DataStoreDataForm> {
if (cachedStore) return cachedStore;

const dataStore = api.dataStore(Namespaces.D2_AUTOGEN_FORMS);
if (!dataSetCode) throw Error(`Unable to load configuration: dataSetCode not defined`);
if (!dataSetCode) {
console.warn(`Unable to load configuration: dataSetCode not defined`);
return new Dhis2DataStoreDataForm({
optionSets: [],
constants: [],
custom: defaultDataStoreConfig,
subNationals: [],
});
}
const storeValue = await dataStore.get<object>(dataSetCode).getData();
if (!storeValue)
return new Dhis2DataStoreDataForm({
Expand Down Expand Up @@ -350,8 +356,7 @@ export class Dhis2DataStoreDataForm {
},
});

cachedStore = new Dhis2DataStoreDataForm(config);
return cachedStore;
return new Dhis2DataStoreDataForm(config);
}

private static async getOptionSets(api: D2Api, storeConfig: DataFormStoreConfig["custom"]): Promise<OptionSet[]> {
Expand Down Expand Up @@ -651,8 +656,6 @@ interface Constant {
displayDescription: string;
}

let cachedStore: Dhis2DataStoreDataForm | undefined;

function sectionConfig<T extends Record<string, Codec<any>>>(properties: T) {
return optional(record(string, Codec.interface(properties)));
}
Expand Down
16 changes: 13 additions & 3 deletions src/data/common/Dhis2DataValueRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,17 @@ export class Dhis2DataValueRepository implements DataValueRepository {
})
.getData();

const dataElements = await this.getDataElements(dataValues);
const dataSetResponse = await this.api.models.dataSets
.get({
fields: { id: true, code: true },
filter: { id: { eq: options.dataSetId } },
})
.getData();

const dataSetCode = dataSetResponse.objects[0]?.code;
if (!dataSetCode) throw new Error(`Data set not found: ${options.dataSetId}`);

const dataElements = await this.getDataElements(dataValues, dataSetCode);

const dataValuesFiles = await this.getFileResourcesMapping(dataElements, dataValues);

Expand Down Expand Up @@ -161,9 +171,9 @@ export class Dhis2DataValueRepository implements DataValueRepository {
);
}

private async getDataElements(dataValues: DataValueSetsDataValue[]) {
private async getDataElements(dataValues: DataValueSetsDataValue[], dataSetCode: string) {
const dataElementIds = dataValues.map(dv => dv.dataElement);
return new Dhis2DataElement(this.api).get(dataElementIds);
return new Dhis2DataElement(this.api).get(dataElementIds, dataSetCode);
}

async save(dataValue: DataValue): Promise<DataValue> {
Expand Down
2 changes: 1 addition & 1 deletion src/domain/common/entities/DataValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
DataElementText,
} from "./DataElement";

interface DataValueBase {
export interface DataValueBase {
orgUnitId: Id;
period: Period;
categoryOptionComboId: Id;
Expand Down
2 changes: 1 addition & 1 deletion src/domain/common/entities/ValidationRule.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Maybe } from "../../../utils/ts-utils";
import { Id } from "./Base";

const allowedRules = [
export const allowedRules = [
{
name: "equal_to",
operator: "=",
Expand Down
19 changes: 19 additions & 0 deletions src/domain/common/entities/__tests__/Config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { getPath } from "../OrgUnit";
import { getMainUserPaths } from "../Config";
import { config } from "./configFixtures";
import { orgUnits } from "./orgUnitFixtures";

describe("Config", () => {
describe("getMainUserPaths", () => {
it("should return the compacted result of getPath", () => {
const result = getMainUserPaths(config);
expect(result).toEqual([getPath(orgUnits)]);
});

it("should handle empty paths correctly", () => {
const userWithoutOrgUnits = { ...config.currentUser, orgUnits: [] };
const result = getMainUserPaths({ ...config, currentUser: userWithoutOrgUnits });
expect(result).toEqual([]);
});
});
});
37 changes: 37 additions & 0 deletions src/domain/common/entities/__tests__/DataElement.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { getDataElementWithCode, DataElement, DataElementBoolean, DataElementText } from "../DataElement";
import { dataElementText as dataElementBase } from "./dataFixtures";

describe("DataElement", () => {
const dataElementBoolean: DataElementBoolean = {
...dataElementBase,
id: "1",
code: "DE_BOOLEAN",
name: "Boolean Data Element",
type: "BOOLEAN",
isTrueOnly: false,
};

const dataElementText: DataElementText = {
...dataElementBase,
id: "2",
code: "DE_TEXT",
name: "Text Data Element",
type: "TEXT",
};

const dataElements: DataElement[] = [dataElementBoolean, dataElementText];

describe("getDataElementWithCode", () => {
it("should return the correct DataElement when a matching code is found", () => {
const codeToFind = "DE_BOOLEAN";
const result = getDataElementWithCode(dataElements, codeToFind);
expect(result).toEqual(dataElementBoolean);
});

it("should return undefined when no matching code is found", () => {
const codeToFind = "NON_EXISTENT_CODE";
const result = getDataElementWithCode(dataElements, codeToFind);
expect(result).toBeUndefined();
});
});
});
88 changes: 88 additions & 0 deletions src/domain/common/entities/__tests__/DataForm.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Id } from "../Base";
import { DataFormM, DataForm, SectionWithPeriods, SectionSimple } from "../DataForm";
import { Period } from "../DataValue";
import { sectionBase, dataFormBase } from "./dataFixtures";

describe("DataFormM", () => {
describe("getReferencedPeriods", () => {
it("should return basePeriod when there are no additional periods", () => {
const dataForm: DataForm = {
...dataFormBase,
sections: [],
};

const basePeriod: Period = "202101";
const result = DataFormM.getReferencedPeriods(dataForm, basePeriod);

expect(result).toEqual([basePeriod]);
});

it("should return unique periods from sections and basePeriod", () => {
const sections: SectionWithPeriods[] = [
createSectionWithPeriods("1", ["202102", "202103"]),
createSectionWithPeriods("2", ["202104", "202105"]),
];

const dataForm: DataForm = {
...dataFormBase,
sections,
};

const basePeriod: Period = "202101";
const result = DataFormM.getReferencedPeriods(dataForm, basePeriod);

expect(result).toEqual(["202101", "202102", "202103", "202104", "202105"]);
});

it("should handle mixed section types", () => {
const sections = [createSectionWithPeriods("1", ["202102", "202103"]), createSectionSimple("2")];

const dataForm: DataForm = {
...dataFormBase,
sections,
};

const basePeriod: Period = "202101";
const result = DataFormM.getReferencedPeriods(dataForm, basePeriod);

expect(result).toEqual(["202101", "202102", "202103"]);
});

it("should return sorted unique periods", () => {
const sections = [
createSectionWithPeriods("1", ["202103", "202102"]),
createSectionWithPeriods("2", ["202105", "202104"]),
createSectionWithPeriods("3", ["202101", "202106"]),
];

const dataForm: DataForm = {
...dataFormBase,
sections,
};

const basePeriod: Period = "202100";
const result = DataFormM.getReferencedPeriods(dataForm, basePeriod);

expect(result).toEqual(["202100", "202101", "202102", "202103", "202104", "202105", "202106"]);
});
});
});

function createSectionSimple(id: Id): SectionSimple {
return {
id,
name: `Section ${id}`,
viewType: "grid-with-combos",
...sectionBase,
};
}

function createSectionWithPeriods(id: Id, periods: Period[]): SectionWithPeriods {
return {
id,
name: `Section ${id}`,
viewType: "grid-with-periods",
periods,
...sectionBase,
};
}
48 changes: 48 additions & 0 deletions src/domain/common/entities/__tests__/DataValue.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { DataValueStore } from "../DataValue";
import { dataElementText, dataValues, dataValueTextSingle, dataValueBase } from "./dataFixtures";

describe("DataValueStore", () => {
describe("DataValueStore.from", () => {
it("should create a DataValueStore from an array of DataValues", () => {
const store = DataValueStore.from(dataValues);
expect(store).toBeInstanceOf(DataValueStore);
expect(store.store).toEqual({
"1.202101.coc1.org1": dataValueTextSingle,
});
});
});

describe("DataValueStore.set", () => {
it("should set a DataValue in the store", () => {
const store = new DataValueStore({});
const updatedStore = store.set(dataValueTextSingle);
expect(updatedStore.store).toEqual({
"1.202101.coc1.org1": dataValueTextSingle,
});
});
});

describe("DataValueStore.get", () => {
it("should get a DataValue from the store", () => {
const store = DataValueStore.from(dataValues);
const dataValue = store.get(dataElementText, dataValueBase);
expect(dataValue).toEqual(dataValueTextSingle);
});
});

describe("DataValueStore.getOrEmpty", () => {
it("should get a DataValue from the store", () => {
const store = DataValueStore.from(dataValues);
const dataValue = store.getOrEmpty(dataElementText, dataValueBase);
expect(dataValue).toEqual(dataValueTextSingle);
});

it("should return an empty DataValue if not in the store", () => {
const store = new DataValueStore({});
const dataValue = store.getOrEmpty(dataElementText, dataValueBase);

const emptyDataValue = { ...dataValueTextSingle, value: "" };
expect(dataValue).toEqual(emptyDataValue);
});
});
});
37 changes: 37 additions & 0 deletions src/domain/common/entities/__tests__/Indicator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Indicator, checkIndicatorDirection, getIndicatorRelatedToDataElement, IndicatorDirection } from "../Indicator";
import { indicatorAfter, indicatorBefore, indicators } from "./indicatorFixtures";

describe("Indicator", () => {
describe("checkIndicatorDirection", () => {
it("should return true when the indicator's direction matches the provided direction", () => {
const direction: IndicatorDirection = "after";

// indicatorAfter indicator contain { direction: "after" }
const result = checkIndicatorDirection(indicatorAfter, direction);
expect(result).toBe(true);
});

it("should return false when the indicator's direction does not match the provided direction", () => {
const direction: IndicatorDirection = "after";

// indicatorBefore indicator contain { direction: "before" }
const result = checkIndicatorDirection(indicatorBefore, direction);
expect(result).toBe(false);
});
});

describe("getIndicatorRelatedToDataElement", () => {
it("should return the indicator related to the specified data element code", () => {
const codeToFind = "DE_1";
const result = getIndicatorRelatedToDataElement(indicators, codeToFind);
// indicatorAfter indicator contain dataElement: { code: "DE_1", direction: "after" }
expect(result).toEqual(indicatorAfter);
});

it("should return undefined when no indicator is related to the specified data element code", () => {
const codeToFind = "NON_EXISTENT_CODE";
const result = getIndicatorRelatedToDataElement(indicators, codeToFind);
expect(result).toBeUndefined();
});
});
});
Loading

0 comments on commit 787dcf1

Please sign in to comment.