diff --git a/frontend/server/data/tinybird/forks-data-source.test.ts b/frontend/server/data/tinybird/forks-data-source.test.ts new file mode 100644 index 000000000..9b402523b --- /dev/null +++ b/frontend/server/data/tinybird/forks-data-source.test.ts @@ -0,0 +1,120 @@ +import { + describe, test, expect, vi, beforeEach +} from 'vitest'; +import {DateTime} from "luxon"; +import { + mockCurrentSummaryData, + mockPreviousSummaryData, + mockCurrentCumulativeTimeseries, + mockCurrentNewTimeseries, +} from '../../mocks/tinybird-forks-response.mock'; +import {ActivityFilterActivityType, ActivityFilterCountType, FilterGranularity} from "../types"; +import type {ForksData} from "~~/types/popularity/responses.types"; + +const mockFetchFromTinybird = vi.fn(); + +describe('Forks Data Source', () => { + beforeEach(() => { + mockFetchFromTinybird.mockClear(); + + // Here be dragons! vi.doMock is not hoisted, and thus it is executed after the original import statement. + // This means that the import for tinybird.ts inside active-contributors-data-source.ts would still be used, + // and thus not mocked. This means we need to import the module again after the mock is set, whenever we want to + // use it. + vi.doMock(import("./tinybird"), () => ({ + fetchFromTinybird: mockFetchFromTinybird, + })); + }) + + test('should fetch cumulative forks data with correct parameters', async () => { + // We have to import this here again because vi.doMock is not hoisted. See the explanation in beforeEach(). + const {fetchForksActivities} = await import("~~/server/data/tinybird/forks-data-source"); + + mockFetchFromTinybird.mockResolvedValueOnce(mockCurrentSummaryData) + .mockResolvedValueOnce(mockPreviousSummaryData) + .mockResolvedValueOnce(mockCurrentCumulativeTimeseries); + + const startDate = DateTime.utc(2024, 3, 20); + const endDate = DateTime.utc(2025, 3, 20); + + const filter = { + granularity: FilterGranularity.WEEKLY, + project: 'the-linux-kernel-organization', + countType: ActivityFilterCountType.CUMULATIVE, + activityType: ActivityFilterActivityType.FORKS, + onlyContributions: false, + startDate, + endDate + }; + + const result = await fetchForksActivities(filter); + + const currentCumulativeCount = mockCurrentSummaryData.data[0]?.activityCount || 0; + const previousCumulativeCount = mockPreviousSummaryData.data[0]?.activityCount || 0; + + const expectedResult: ForksData = { + summary: { + current: currentCumulativeCount, + previous: previousCumulativeCount, + percentageChange: 100, + changeValue: currentCumulativeCount - previousCumulativeCount, + periodFrom: filter.startDate?.toString(), + periodTo: filter.endDate?.toString(), + }, + data: mockCurrentCumulativeTimeseries.data.map((item) => ({ + startDate: item.startDate, + endDate: item.endDate, + forks: item.cumulativeActivityCount, + })) + }; + + expect(result).toEqual(expectedResult); + }); + + test('should fetch new forks data with correct parameters', async () => { + // We have to import this here again because vi.doMock is not hoisted. See the explanation in beforeEach(). + const {fetchForksActivities} = await import("~~/server/data/tinybird/forks-data-source"); + + mockFetchFromTinybird.mockResolvedValueOnce(mockCurrentSummaryData) + .mockResolvedValueOnce(mockPreviousSummaryData) + .mockResolvedValueOnce(mockCurrentNewTimeseries); + + const startDate = DateTime.utc(2024, 3, 20); + const endDate = DateTime.utc(2025, 3, 20); + + const filter = { + granularity: FilterGranularity.WEEKLY, + project: 'the-linux-kernel-organization', + countType: ActivityFilterCountType.NEW, + activityType: ActivityFilterActivityType.FORKS, + onlyContributions: false, + startDate, + endDate + }; + + const result = await fetchForksActivities(filter); + + const currentCumulativeCount = mockCurrentSummaryData.data[0]?.activityCount || 0; + const previousCumulativeCount = mockPreviousSummaryData.data[0]?.activityCount || 0; + + const expectedResult: ForksData = { + summary: { + current: currentCumulativeCount, + previous: previousCumulativeCount, + percentageChange: 100, + changeValue: currentCumulativeCount - previousCumulativeCount, + periodFrom: filter.startDate?.toString(), + periodTo: filter.endDate?.toString(), + }, + data: mockCurrentNewTimeseries.data.map((item) => ({ + startDate: item.startDate, + endDate: item.endDate, + forks: item.activityCount, + })) + }; + + expect(result).toEqual(expectedResult); + }); + + // TODO: Add checks for invalid dates, invalid data, sql injections, and other edge cases. +}); diff --git a/frontend/server/mocks/tinybird-forks-response.mock.ts b/frontend/server/mocks/tinybird-forks-response.mock.ts new file mode 100644 index 000000000..948fd1bec --- /dev/null +++ b/frontend/server/mocks/tinybird-forks-response.mock.ts @@ -0,0 +1,219 @@ +export const mockCurrentSummaryData = { + meta: [ + { + name: "activityCount", + type: "UInt64" + } + ], + data: [ + { + activityCount: 100 + } + ], + rows: 1, + statistics: { + elapsed: 0.320078864, + rows_read: 357628, + bytes_read: 18362864 + } +}; + +export const mockPreviousSummaryData = { + meta: [ + { + name: "activityCount", + type: "UInt64" + } + ], + data: [ + { + activityCount: 50 + } + ], + rows: 1, + statistics: { + elapsed: 0.279353225, + rows_read: 474679, + bytes_read: 24215414 + } +}; + +export const mockCurrentCumulativeTimeseries = { + meta: [ + { + name: "startDate", + type: "Nullable(Date)" + }, + { + name: "endDate", + type: "Nullable(Date)" + }, + { + name: "cumulativeActivityCount", + type: "Nullable(UInt64)" + } + ], + data: [ + { + startDate: "2024-03-01", + endDate: "2024-03-31", + cumulativeActivityCount: 0 + }, + { + startDate: "2024-04-01", + endDate: "2024-04-30", + cumulativeActivityCount: 0 + }, + { + startDate: "2024-05-01", + endDate: "2024-05-31", + cumulativeActivityCount: 0 + }, + { + startDate: "2024-06-01", + endDate: "2024-06-30", + cumulativeActivityCount: 0 + }, + { + startDate: "2024-07-01", + endDate: "2024-07-31", + cumulativeActivityCount: 0 + }, + { + startDate: "2024-08-01", + endDate: "2024-08-31", + cumulativeActivityCount: 0 + }, + { + startDate: "2024-09-01", + endDate: "2024-09-30", + cumulativeActivityCount: 0 + }, + { + startDate: "2024-10-01", + endDate: "2024-10-31", + cumulativeActivityCount: 0 + }, + { + startDate: "2024-11-01", + endDate: "2024-11-30", + cumulativeActivityCount: 0 + }, + { + startDate: "2024-12-01", + endDate: "2024-12-31", + cumulativeActivityCount: 0 + }, + { + startDate: "2025-01-01", + endDate: "2025-01-31", + cumulativeActivityCount: 0 + }, + { + startDate: "2025-02-01", + endDate: "2025-02-28", + cumulativeActivityCount: 0 + }, + { + startDate: "2025-03-01", + endDate: "2025-03-31", + cumulativeActivityCount: 0 + } + ], + rows: 13, + statistics: { + elapsed: 0.680491604, + rows_read: 5847561, + bytes_read: 292784030 + } +}; + +export const mockCurrentNewTimeseries = { + meta: [ + { + name: "startDate", + type: "Nullable(Date)" + }, + { + name: "endDate", + type: "Nullable(Date)" + }, + { + name: "activityCount", + type: "UInt64" + } + ], + data: [ + { + startDate: "2023-03-01", + endDate: "2023-03-31", + activityCount: 0 + }, + { + startDate: "2023-04-01", + endDate: "2023-04-30", + activityCount: 0 + }, + { + startDate: "2023-05-01", + endDate: "2023-05-31", + activityCount: 0 + }, + { + startDate: "2023-06-01", + endDate: "2023-06-30", + activityCount: 0 + }, + { + startDate: "2023-07-01", + endDate: "2023-07-31", + activityCount: 0 + }, + { + startDate: "2023-08-01", + endDate: "2023-08-31", + activityCount: 0 + }, + { + startDate: "2023-09-01", + endDate: "2023-09-30", + activityCount: 0 + }, + { + startDate: "2023-10-01", + endDate: "2023-10-31", + activityCount: 0 + }, + { + startDate: "2023-11-01", + endDate: "2023-11-30", + activityCount: 0 + }, + { + startDate: "2023-12-01", + endDate: "2023-12-31", + activityCount: 0 + }, + { + startDate: "2024-01-01", + endDate: "2024-01-31", + activityCount: 0 + }, + { + startDate: "2024-02-01", + endDate: "2024-02-29", + activityCount: 0 + }, + { + startDate: "2024-03-01", + endDate: "2024-03-31", + activityCount: 0 + } + ], + rows: 13, + statistics: { + elapsed: 0.031928949, + rows_read: 475680, + bytes_read: 24223415 + } +};