Aam-Digital/ndb-core

View on GitHub
src/app/features/reporting/data-aggregation.service.spec.ts

Summary

Maintainability
F
4 days
Test Coverage
import { TestBed } from "@angular/core/testing";

import {
  Aggregation,
  DataAggregationService,
} from "./data-aggregation.service";
import { QueryService } from "../../core/export/query.service";
import { EventNote } from "../../child-dev-project/attendance/model/event-note";
import moment from "moment";
import { ChildSchoolRelation } from "../../child-dev-project/children/model/childSchoolRelation";
import { genders } from "../../child-dev-project/children/model/genders";
import { mockEntityMapper } from "../../core/entity/entity-mapper/mock-entity-mapper-service";
import { entityRegistry } from "../../core/entity/database-entity.decorator";
import { createEntityOfType } from "../../core/demo-data/create-entity-of-type";
import { TestEntity } from "../../utils/test-utils/TestEntity";

describe("DataAggregationService", () => {
  let service: DataAggregationService;
  let mockQueryService: jasmine.SpyObj<QueryService>;
  beforeEach(() => {
    mockQueryService = jasmine.createSpyObj(["queryData", "cacheRequiredData"]);
    TestBed.configureTestingModule({
      providers: [{ provide: QueryService, useValue: mockQueryService }],
    });
    service = TestBed.inject(DataAggregationService);
  });

  it("should be created", () => {
    expect(service).toBeTruthy();
  });

  it("should run the aggregation queries and return the results", async () => {
    const baseQuery = `Child:toArray`;
    const christiansQuery = "[*religion=christian]";
    const muslimsQuery = "[*religion=muslim]";
    const childDisaggregation: Aggregation = {
      query: baseQuery,
      aggregations: [
        { label: "christians", query: christiansQuery },
        { label: "muslims", query: muslimsQuery },
      ],
    };
    const baseData = [createEntityOfType("School")];
    mockQueryService.queryData.and.returnValues(
      baseData,
      [createEntityOfType("School")],
      [createEntityOfType("School"), createEntityOfType("School")],
    );

    const report = await service.calculateReport([childDisaggregation]);
    expect(mockQueryService.queryData.calls.allArgs()).toEqual([
      [baseQuery, undefined, undefined, undefined],
      [christiansQuery, undefined, undefined, baseData],
      [muslimsQuery, undefined, undefined, baseData],
    ]);
    expect(report).toEqual([
      {
        header: { label: "christians", groupedBy: [], result: 1 },
        subRows: [],
      },
      { header: { label: "muslims", groupedBy: [], result: 2 }, subRows: [] },
    ]);
  });

  it("should add the date to each query", async () => {
    const baseQueryString = `${EventNote.ENTITY_TYPE}:toArray[*date>=? date<?]`;
    const firstDate = moment().subtract(1, "month").toDate();
    const secondDate = moment().subtract(1, "week").toDate();
    const subjectQueryString = `[*subject=test]`;
    const disaggregation: Aggregation = {
      query: baseQueryString,
      aggregations: [{ label: "tests", query: subjectQueryString }],
    };

    await service.calculateReport([disaggregation], firstDate, secondDate);
    expect(mockQueryService.queryData.calls.allArgs()).toEqual([
      [baseQueryString, firstDate, secondDate, undefined],
      [subjectQueryString, firstDate, secondDate, undefined],
    ]);
  });

  it("should create queries for nested aggregations", async () => {
    const baseQuery = `School:toArray`;
    const nestedBaseQuery = `[*private=true]:getRelated(${ChildSchoolRelation.ENTITY_TYPE}, schoolId):getActive`;
    const firstNestedAggregation = `[*schoolClass>3]`;
    const secondNestedAggregation = `[*schoolClass<=3]`;
    const normalAggregation = `[*privateSchool=true]`;
    const aggregation: Aggregation = {
      query: baseQuery,
      label: "Base result",
      aggregations: [
        {
          query: nestedBaseQuery,
          aggregations: [
            {
              label: "First nested aggregation",
              query: firstNestedAggregation,
            },
            {
              label: "Second nested aggregation",
              query: secondNestedAggregation,
            },
          ],
        },
        { label: "Normal aggregation", query: normalAggregation },
      ],
    };

    const baseData = [
      createEntityOfType("School"),
      createEntityOfType("School"),
    ];
    const nestedData = [new ChildSchoolRelation()];
    mockQueryService.queryData.and.callFake((query) => {
      switch (query) {
        case baseQuery:
          return baseData;
        case nestedBaseQuery:
          return nestedData;
        default:
          return [createEntityOfType("School")];
      }
    });
    const result = await service.calculateReport([aggregation]);
    expect(mockQueryService.queryData.calls.allArgs()).toEqual([
      [baseQuery, undefined, undefined, undefined],
      [nestedBaseQuery, undefined, undefined, baseData],
      [firstNestedAggregation, undefined, undefined, nestedData],
      [secondNestedAggregation, undefined, undefined, nestedData],
      [normalAggregation, undefined, undefined, baseData],
    ]);
    expect(result).toEqual([
      {
        header: {
          label: "Base result",
          groupedBy: [],
          result: 2,
        },
        subRows: [
          {
            header: {
              label: "First nested aggregation",
              groupedBy: [],
              result: 1,
            },
            subRows: [],
          },
          {
            header: {
              label: "Second nested aggregation",
              groupedBy: [],
              result: 1,
            },
            subRows: [],
          },
          {
            header: { label: "Normal aggregation", groupedBy: [], result: 1 },
            subRows: [],
          },
        ],
      },
    ]);
  });

  it("should correctly parse groupBy results", async () => {
    const maleChild = new TestEntity();
    maleChild.category = genders[1];
    const femaleChild = new TestEntity();
    femaleChild.category = genders[2];
    mockQueryService.queryData.and.returnValue([
      femaleChild,
      maleChild,
      maleChild,
    ]);
    const groupByAggregation: Aggregation = {
      query: TestEntity.ENTITY_TYPE,
      groupBy: ["category"],
      label: "Total # of children",
    };

    const result = await service.calculateReport([groupByAggregation]);

    expect(result).toEqual([
      {
        header: { label: "Total # of children", groupedBy: [], result: 3 },
        subRows: [
          {
            header: {
              label: "Total # of children",
              groupedBy: [{ property: "category", value: genders[2] }],
              result: 1,
            },
            subRows: [],
          },
          {
            header: {
              label: "Total # of children",
              groupedBy: [{ property: "category", value: genders[1] }],
              result: 2,
            },
            subRows: [],
          },
        ],
      },
    ]);
  });

  it("should run aggregations after a groupBy", async () => {
    const maleChild = new TestEntity();
    maleChild.category = genders[1];
    const femaleChild = new TestEntity();
    femaleChild.category = genders[2];
    mockQueryService.queryData.and.returnValue([
      maleChild,
      femaleChild,
      maleChild,
    ]);
    const groupByAggregation: Aggregation = {
      query: TestEntity.ENTITY_TYPE,
      groupBy: ["category"],
      label: "Total # of children",
      aggregations: [
        { query: `[*name=christian]`, label: "Total # of christians" },
      ],
    };

    const result = await service.calculateReport([groupByAggregation]);

    expect(result).toEqual([
      {
        header: { label: "Total # of children", groupedBy: [], result: 3 },
        subRows: [
          {
            header: {
              label: "Total # of christians",
              groupedBy: [],
              result: 3,
            },
            subRows: [],
          },
          {
            header: {
              label: "Total # of children",
              groupedBy: [{ property: "category", value: genders[1] }],
              result: 2,
            },
            subRows: [
              {
                header: {
                  label: "Total # of christians",
                  groupedBy: [{ property: "category", value: genders[1] }],
                  result: 3,
                },
                subRows: [],
              },
            ],
          },
          {
            header: {
              label: "Total # of children",
              groupedBy: [{ property: "category", value: genders[2] }],
              result: 1,
            },
            subRows: [
              {
                header: {
                  label: "Total # of christians",
                  groupedBy: [{ property: "category", value: genders[2] }],
                  result: 3,
                },
                subRows: [],
              },
            ],
          },
        ],
      },
    ]);
  });

  it("should support groupBy with an array of properties", async () => {
    const n1 = "name_1";
    const n2 = "name_2";
    const male1 = new TestEntity();
    male1.category = genders[1];
    male1.name = n1;
    const male2 = new TestEntity();
    male2.category = genders[1];
    male2.name = n2;
    const female1 = new TestEntity();
    female1.category = genders[2];
    female1.name = n1;
    const female1b = new TestEntity();
    female1b.category = genders[2];
    female1b.name = n1;
    mockQueryService.queryData.and.returnValue([
      female1b,
      male1,
      female1,
      male2,
    ]);
    const groupByAggregation: Aggregation = {
      query: TestEntity.ENTITY_TYPE,
      groupBy: ["category", "name"],
      label: "Total # of children",
    };
    const result = await service.calculateReport([groupByAggregation]);

    expect(result).toEqual([
      {
        header: {
          label: "Total # of children",
          groupedBy: [],
          result: 4,
        },
        subRows: [
          {
            header: {
              label: "Total # of children",
              groupedBy: [
                {
                  property: "name",
                  value: n1,
                },
              ],
              result: 3,
            },
            subRows: [],
          },
          {
            header: {
              label: "Total # of children",
              groupedBy: [
                {
                  property: "name",
                  value: n2,
                },
              ],
              result: 1,
            },
            subRows: [],
          },
          {
            header: {
              label: "Total # of children",
              groupedBy: [
                {
                  property: "category",
                  value: genders[2],
                },
              ],
              result: 2,
            },
            subRows: [
              {
                header: {
                  label: "Total # of children",
                  groupedBy: [
                    {
                      property: "category",
                      value: genders[2],
                    },
                    {
                      property: "name",
                      value: n1,
                    },
                  ],
                  result: 2,
                },
                subRows: [],
              },
            ],
          },
          {
            header: {
              label: "Total # of children",
              groupedBy: [
                {
                  property: "category",
                  value: genders[1],
                },
              ],
              result: 2,
            },
            subRows: [
              {
                header: {
                  label: "Total # of children",
                  groupedBy: [
                    {
                      property: "category",
                      value: genders[1],
                    },
                    {
                      property: "name",
                      value: n1,
                    },
                  ],
                  result: 1,
                },
                subRows: [],
              },
              {
                header: {
                  label: "Total # of children",
                  groupedBy: [
                    {
                      property: "category",
                      value: genders[1],
                    },
                    {
                      property: "name",
                      value: n2,
                    },
                  ],
                  result: 1,
                },
                subRows: [],
              },
            ],
          },
        ],
      },
    ]);
  });

  it("should allow multiple groupBy's", async () => {
    const femaleMuslim = new TestEntity();
    femaleMuslim.category = genders[2];
    femaleMuslim.name = "muslim";
    const femaleChristian = new TestEntity();
    femaleChristian.category = genders[2];
    femaleChristian.name = "christian";
    const maleMuslim = new TestEntity();
    maleMuslim.category = genders[1];
    maleMuslim.name = "muslim";
    mockQueryService.queryData.and.returnValue([
      femaleChristian,
      femaleMuslim,
      maleMuslim,
      femaleMuslim,
      femaleChristian,
    ]);

    const nestedGroupBy: Aggregation = {
      query: `Child`,
      groupBy: ["category"],
      label: "Total # of children",
      aggregations: [
        {
          query: `[*isActive = true]`,
          groupBy: ["name"],
          label: "Total # of old children",
        },
      ],
    };
    const result = await service.calculateReport([nestedGroupBy]);

    expect(result).toEqual([
      {
        header: { label: "Total # of children", groupedBy: [], result: 5 },
        subRows: [
          {
            header: {
              label: "Total # of old children",
              groupedBy: [],
              result: 5,
            },
            subRows: [
              {
                header: {
                  label: "Total # of old children",
                  groupedBy: [{ property: "name", value: "christian" }],
                  result: 2,
                },
                subRows: [],
              },
              {
                header: {
                  label: "Total # of old children",
                  groupedBy: [{ property: "name", value: "muslim" }],
                  result: 3,
                },
                subRows: [],
              },
            ],
          },
          {
            header: {
              label: "Total # of children",
              groupedBy: [{ property: "category", value: genders[2] }],
              result: 4,
            },
            subRows: [
              {
                header: {
                  label: "Total # of old children",
                  groupedBy: [{ property: "category", value: genders[2] }],
                  result: 5,
                },
                subRows: [
                  {
                    header: {
                      label: "Total # of old children",
                      groupedBy: [
                        { property: "category", value: genders[2] },
                        { property: "name", value: "christian" },
                      ],
                      result: 2,
                    },
                    subRows: [],
                  },
                  {
                    header: {
                      label: "Total # of old children",
                      groupedBy: [
                        { property: "category", value: genders[2] },
                        { property: "name", value: "muslim" },
                      ],
                      result: 3,
                    },
                    subRows: [],
                  },
                ],
              },
            ],
          },
          {
            header: {
              label: "Total # of children",
              groupedBy: [{ property: "category", value: genders[1] }],
              result: 1,
            },
            subRows: [
              {
                header: {
                  label: "Total # of old children",
                  groupedBy: [{ property: "category", value: genders[1] }],
                  result: 5,
                },
                subRows: [
                  {
                    header: {
                      label: "Total # of old children",
                      groupedBy: [
                        { property: "category", value: genders[1] },
                        { property: "name", value: "christian" },
                      ],
                      result: 2,
                    },
                    subRows: [],
                  },
                  {
                    header: {
                      label: "Total # of old children",
                      groupedBy: [
                        { property: "category", value: genders[1] },
                        { property: "name", value: "muslim" },
                      ],
                      result: 3,
                    },
                    subRows: [],
                  },
                ],
              },
            ],
          },
        ],
      },
    ]);
  });

  it("should handle subfields of filtered query anywhere in the reporting structure", async () => {
    const c1 = new TestEntity();
    c1.name = "1";

    const entityMapper = mockEntityMapper([c1]);
    const queryService = new QueryService(
      entityMapper,
      null,
      null,
      entityRegistry,
    );
    service = new DataAggregationService(queryService);

    const complexQuery: Aggregation = {
      label: "!!",
      query: "TestEntity:toArray.name",
    };
    const otherQuery: Aggregation = {
      label: "other",
      query: "OtherEntity:toArray",
    };

    const result = await service.calculateReport([complexQuery, otherQuery]);

    expect(result).toEqual([
      {
        header: { label: "!!", groupedBy: [], result: 1 },
        subRows: [],
      },
      { header: { label: "other", groupedBy: [], result: 0 }, subRows: [] },
    ]);
  });
});