test: remove stdout and std from test by mocking logger module (#161)

- Same as #143 
- Resolves
https://github.com/stats-organization/github-stats-extended/pull/143#discussion_r3105155487
- Closes #141
This commit is contained in:
Marco Pasqualetti
2026-04-19 09:28:46 +02:00
committed by GitHub
parent d476f8cbe1
commit fc4ee7507b
21 changed files with 450 additions and 262 deletions
+42 -43
View File
@@ -1,26 +1,27 @@
// @ts-check
import { api, getConfig } from "@stats-organization/github-readme-stats-core";
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
api: vi.fn(),
storeRequest: vi.fn(),
getUserAccessByName: vi.fn(),
config: {},
}));
vi.mock("@stats-organization/github-readme-stats-core", async () => {
const { mockCore } = await import("./utils.js");
return mockCore({ api: mocks.api, getConfig: () => mocks.config });
});
vi.mock("../src/common/database.js", () => ({
storeRequest: mocks.storeRequest,
getUserAccessByName: mocks.getUserAccessByName,
}));
import router from "../router.js";
import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
import { getUserAccessByName, storeRequest } from "../src/common/database.js";
vi.mock(import("@stats-organization/github-readme-stats-core"), async () => {
const { mockCore } = await import("./utils.js");
return mockCore();
});
vi.mock(import("../src/common/database.js"), async (importOriginal) => ({
...(await importOriginal()),
storeRequest: vi.fn(),
getUserAccessByName: vi.fn(),
}));
const apiMock = vi.mocked(api);
const getConfigMock = vi.mocked(getConfig);
const storeRequestMock = vi.mocked(storeRequest);
const getUserAccessByNameMock = vi.mocked(getUserAccessByName);
const createRequest = (search = "") => ({
headers: {},
@@ -43,18 +44,18 @@ const errorCacheHeader =
`stale-while-revalidate=${DURATIONS.ONE_DAY}`;
beforeEach(() => {
mocks.api.mockReset();
mocks.storeRequest.mockReset().mockResolvedValue(undefined);
mocks.getUserAccessByName.mockReset().mockResolvedValue(null);
mocks.config = {};
apiMock.mockReset();
getConfigMock.mockReset().mockReturnValue({});
storeRequestMock.mockReset().mockResolvedValue(undefined);
getUserAccessByNameMock.mockReset().mockResolvedValue(null);
// CACHE_SECONDS is not set here, this is just to safeguard against CACHE_SECONDS being set externally
delete process.env.CACHE_SECONDS;
});
describe("Test /api backend routing", () => {
it("happy path should pass query params and user PAT, respond with stats content and persist request", async () => {
mocks.getUserAccessByName.mockResolvedValue({ token: "user-pat" });
mocks.api.mockResolvedValue({
getUserAccessByNameMock.mockResolvedValue({ token: "user-pat" });
apiMock.mockResolvedValue({
status: "success",
content: "mock-stats-svg",
});
@@ -66,8 +67,8 @@ describe("Test /api backend routing", () => {
await router(req, res);
expect(mocks.getUserAccessByName).toHaveBeenCalledWith("anuraghazra");
expect(mocks.api).toHaveBeenCalledWith(
expect(getUserAccessByNameMock).toHaveBeenCalledWith("anuraghazra");
expect(apiMock).toHaveBeenCalledWith(
{
username: "anuraghazra",
theme: "dark",
@@ -85,11 +86,11 @@ describe("Test /api backend routing", () => {
["Content-Type", "image/svg+xml"],
]);
expect(res.end).toHaveBeenCalledExactlyOnceWith("mock-stats-svg");
expect(mocks.storeRequest).toHaveBeenCalledExactlyOnceWith(req);
expect(storeRequestMock).toHaveBeenCalledExactlyOnceWith(req);
});
it("should use the shorter error cache for temporary stats errors", async () => {
mocks.api.mockResolvedValue({
apiMock.mockResolvedValue({
status: "error - temporary",
content: "temporary-error-svg",
});
@@ -99,8 +100,8 @@ describe("Test /api backend routing", () => {
await router(req, res);
expect(mocks.getUserAccessByName).toHaveBeenCalledWith("anuraghazra");
expect(mocks.api).toHaveBeenCalledWith(
expect(getUserAccessByNameMock).toHaveBeenCalledWith("anuraghazra");
expect(apiMock).toHaveBeenCalledWith(
{
username: "anuraghazra",
},
@@ -111,11 +112,11 @@ describe("Test /api backend routing", () => {
["Content-Type", "image/svg+xml"],
]);
expect(res.end).toHaveBeenCalledExactlyOnceWith("temporary-error-svg");
expect(mocks.storeRequest).toHaveBeenCalledExactlyOnceWith(req);
expect(storeRequestMock).toHaveBeenCalledExactlyOnceWith(req);
});
it("should not persist permanent stats errors returned by core", async () => {
mocks.api.mockResolvedValue({
apiMock.mockResolvedValue({
status: "error - permanent",
content: "permanent-error-svg",
});
@@ -125,8 +126,8 @@ describe("Test /api backend routing", () => {
await router(req, res);
expect(mocks.getUserAccessByName).toHaveBeenCalledWith("anuraghazra");
expect(mocks.api).toHaveBeenCalledWith(
expect(getUserAccessByNameMock).toHaveBeenCalledWith("anuraghazra");
expect(apiMock).toHaveBeenCalledWith(
{
username: "anuraghazra",
},
@@ -137,7 +138,7 @@ describe("Test /api backend routing", () => {
["Content-Type", "image/svg+xml"],
]);
expect(res.end).toHaveBeenCalledExactlyOnceWith("permanent-error-svg");
expect(mocks.storeRequest).not.toHaveBeenCalled();
expect(storeRequestMock).not.toHaveBeenCalled();
});
it("should reject blacklisted usernames before calling core logic", async () => {
@@ -146,8 +147,8 @@ describe("Test /api backend routing", () => {
await router(req, res);
expect(mocks.api).not.toHaveBeenCalled();
expect(mocks.getUserAccessByName).not.toHaveBeenCalled();
expect(apiMock).not.toHaveBeenCalled();
expect(getUserAccessByNameMock).not.toHaveBeenCalled();
expect(res.setHeader.mock.calls).toEqual([
["Cache-Control", defaultCacheHeader],
["Content-Type", "image/svg+xml"],
@@ -155,21 +156,19 @@ describe("Test /api backend routing", () => {
expect(res.end).toHaveBeenCalledExactlyOnceWith(
"render-error:This username is blacklisted",
);
expect(mocks.storeRequest).not.toHaveBeenCalled();
expect(storeRequestMock).not.toHaveBeenCalled();
});
it("should reject non-whitelisted usernames before calling core logic", async () => {
mocks.config = {
whitelist: ["allowed-user"],
};
getConfigMock.mockReturnValue({ whitelist: ["allowed-user"] });
const req = createRequest("username=blocked-user");
const res = createResponse();
await router(req, res);
expect(mocks.api).not.toHaveBeenCalled();
expect(mocks.getUserAccessByName).not.toHaveBeenCalled();
expect(apiMock).not.toHaveBeenCalled();
expect(getUserAccessByNameMock).not.toHaveBeenCalled();
expect(res.setHeader.mock.calls).toEqual([
["Cache-Control", defaultCacheHeader],
["Content-Type", "image/svg+xml"],
@@ -177,6 +176,6 @@ describe("Test /api backend routing", () => {
expect(res.end).toHaveBeenCalledExactlyOnceWith(
"render-error:This username is not whitelisted",
);
expect(mocks.storeRequest).not.toHaveBeenCalled();
expect(storeRequestMock).not.toHaveBeenCalled();
});
});
+38 -39
View File
@@ -1,26 +1,27 @@
// @ts-check
import { getConfig, gist } from "@stats-organization/github-readme-stats-core";
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
gist: vi.fn(),
storeRequest: vi.fn(),
getUserAccessByName: vi.fn(),
config: {},
}));
vi.mock("@stats-organization/github-readme-stats-core", async () => {
const { mockCore } = await import("./utils.js");
return mockCore({ gist: mocks.gist, getConfig: () => mocks.config });
});
vi.mock("../src/common/database.js", () => ({
storeRequest: mocks.storeRequest,
getUserAccessByName: mocks.getUserAccessByName,
}));
import router from "../router.js";
import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
import { getUserAccessByName, storeRequest } from "../src/common/database.js";
vi.mock(import("@stats-organization/github-readme-stats-core"), async () => {
const { mockCore } = await import("./utils.js");
return mockCore();
});
vi.mock(import("../src/common/database.js"), async (importOriginal) => ({
...(await importOriginal()),
storeRequest: vi.fn(),
getUserAccessByName: vi.fn(),
}));
const gistMock = vi.mocked(gist);
const getConfigMock = vi.mocked(getConfig);
const storeRequestMock = vi.mocked(storeRequest);
const getUserAccessByNameMock = vi.mocked(getUserAccessByName);
const createRequest = (search) => ({
headers: {},
@@ -43,17 +44,17 @@ const errorCacheHeader =
`stale-while-revalidate=${DURATIONS.ONE_DAY}`;
beforeEach(() => {
mocks.gist.mockReset();
mocks.storeRequest.mockReset().mockResolvedValue(undefined);
mocks.getUserAccessByName.mockReset().mockResolvedValue(null);
mocks.config = {};
gistMock.mockReset();
getConfigMock.mockReset().mockReturnValue({});
storeRequestMock.mockReset().mockResolvedValue(undefined);
getUserAccessByNameMock.mockReset().mockResolvedValue(null);
// CACHE_SECONDS is not set here, this is just to safeguard against CACHE_SECONDS being set externally
delete process.env.CACHE_SECONDS;
});
describe("Test /api/gist backend routing", () => {
it("happy path should pass query params, respond with gist content and persist request", async () => {
mocks.gist.mockResolvedValue({
gistMock.mockResolvedValue({
status: "success",
content: "mock-gist-svg",
});
@@ -63,11 +64,11 @@ describe("Test /api/gist backend routing", () => {
await router(req, res);
expect(mocks.gist).toHaveBeenCalledWith({
expect(gistMock).toHaveBeenCalledWith({
id: "bbfce31e0217a3689c8d961a356cb10d",
theme: "dark",
});
expect(mocks.getUserAccessByName).not.toHaveBeenCalled();
expect(getUserAccessByNameMock).not.toHaveBeenCalled();
expect(req.query).toEqual({
id: "bbfce31e0217a3689c8d961a356cb10d",
theme: "dark",
@@ -77,11 +78,11 @@ describe("Test /api/gist backend routing", () => {
["Content-Type", "image/svg+xml"],
]);
expect(res.end).toHaveBeenCalledExactlyOnceWith("mock-gist-svg");
expect(mocks.storeRequest).toHaveBeenCalledExactlyOnceWith(req);
expect(storeRequestMock).toHaveBeenCalledExactlyOnceWith(req);
});
it("should use the shorter error cache for temporary gist errors", async () => {
mocks.gist.mockResolvedValue({
gistMock.mockResolvedValue({
status: "error - temporary",
content: "temporary-error-svg",
});
@@ -91,20 +92,20 @@ describe("Test /api/gist backend routing", () => {
await router(req, res);
expect(mocks.gist).toHaveBeenCalledWith({
expect(gistMock).toHaveBeenCalledWith({
id: "bbfce31e0217a3689c8d961a356cb10d",
});
expect(mocks.getUserAccessByName).not.toHaveBeenCalled();
expect(getUserAccessByNameMock).not.toHaveBeenCalled();
expect(res.setHeader.mock.calls).toEqual([
["Cache-Control", errorCacheHeader],
["Content-Type", "image/svg+xml"],
]);
expect(res.end).toHaveBeenCalledExactlyOnceWith("temporary-error-svg");
expect(mocks.storeRequest).toHaveBeenCalledExactlyOnceWith(req);
expect(storeRequestMock).toHaveBeenCalledExactlyOnceWith(req);
});
it("should not persist permanent gist errors returned by core", async () => {
mocks.gist.mockResolvedValue({
gistMock.mockResolvedValue({
status: "error - permanent",
content: "permanent-error-svg",
});
@@ -114,30 +115,28 @@ describe("Test /api/gist backend routing", () => {
await router(req, res);
expect(mocks.gist).toHaveBeenCalledWith({
expect(gistMock).toHaveBeenCalledWith({
id: "bbfce31e0217a3689c8d961a356cb10d",
});
expect(mocks.getUserAccessByName).not.toHaveBeenCalled();
expect(getUserAccessByNameMock).not.toHaveBeenCalled();
expect(res.setHeader.mock.calls).toEqual([
["Cache-Control", defaultCacheHeader],
["Content-Type", "image/svg+xml"],
]);
expect(res.end).toHaveBeenCalledExactlyOnceWith("permanent-error-svg");
expect(mocks.storeRequest).not.toHaveBeenCalled();
expect(storeRequestMock).not.toHaveBeenCalled();
});
it("should reject non-whitelisted gist ids before calling core logic", async () => {
mocks.config = {
gistWhitelist: ["allowed-gist-id"],
};
getConfigMock.mockReturnValue({ gistWhitelist: ["allowed-gist-id"] });
const req = createRequest("id=blocked-gist-id");
const res = createResponse();
await router(req, res);
expect(mocks.gist).not.toHaveBeenCalled();
expect(mocks.getUserAccessByName).not.toHaveBeenCalled();
expect(gistMock).not.toHaveBeenCalled();
expect(getUserAccessByNameMock).not.toHaveBeenCalled();
expect(res.setHeader.mock.calls).toEqual([
["Cache-Control", defaultCacheHeader],
["Content-Type", "image/svg+xml"],
@@ -145,6 +144,6 @@ describe("Test /api/gist backend routing", () => {
expect(res.end).toHaveBeenCalledExactlyOnceWith(
"render-error:This gist ID is not whitelisted",
);
expect(mocks.storeRequest).not.toHaveBeenCalled();
expect(storeRequestMock).not.toHaveBeenCalled();
});
});
+18 -1
View File
@@ -4,7 +4,15 @@
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import {
afterAll,
afterEach,
beforeAll,
describe,
expect,
it,
vi,
} from "vitest";
const mock = new MockAdapter(axios);
@@ -63,6 +71,11 @@ beforeAll(async () => {
vi.stubEnv("PAT_3", "testPAT3");
vi.stubEnv("PAT_4", "testPAT4");
const { logger } =
await import("@stats-organization/github-readme-stats-core");
vi.spyOn(logger, "log").mockImplementation(() => {});
vi.spyOn(logger, "error").mockImplementation(() => {});
({ RATE_LIMIT_SECONDS, default: patInfo } =
await import("../api-renamed/status/pat-info.js"));
});
@@ -74,6 +87,10 @@ afterEach(() => {
vi.resetModules();
});
afterAll(() => {
vi.restoreAllMocks();
});
describe("Test /api/status/pat-info", () => {
it("should return only 'validPATs' if all PATs are valid", async () => {
mock
+23 -22
View File
@@ -1,25 +1,26 @@
// @ts-check
import { pin } from "@stats-organization/github-readme-stats-core";
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
pin: vi.fn(),
import router from "../router.js";
import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
import { getUserAccessByName, storeRequest } from "../src/common/database.js";
vi.mock(import("@stats-organization/github-readme-stats-core"), async () => {
const { mockCore } = await import("./utils.js");
return mockCore();
});
vi.mock(import("../src/common/database.js"), async (importOriginal) => ({
...(await importOriginal()),
storeRequest: vi.fn(),
getUserAccessByName: vi.fn(),
}));
vi.mock("@stats-organization/github-readme-stats-core", async () => {
const { mockCore } = await import("./utils.js");
return mockCore({ pin: mocks.pin });
});
vi.mock("../src/common/database.js", () => ({
storeRequest: mocks.storeRequest,
getUserAccessByName: mocks.getUserAccessByName,
}));
import router from "../router.js";
import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
const pinMock = vi.mocked(pin);
const storeRequestMock = vi.mocked(storeRequest);
const getUserAccessByNameMock = vi.mocked(getUserAccessByName);
const createRequest = (search = "") => ({
headers: {},
@@ -37,17 +38,17 @@ const defaultCacheHeader =
`stale-while-revalidate=${DURATIONS.ONE_DAY}`;
beforeEach(() => {
mocks.pin.mockReset();
mocks.storeRequest.mockReset().mockResolvedValue(undefined);
mocks.getUserAccessByName.mockReset().mockResolvedValue(null);
pinMock.mockReset();
storeRequestMock.mockReset().mockResolvedValue(undefined);
getUserAccessByNameMock.mockReset().mockResolvedValue(null);
// CACHE_SECONDS is not set here, this is just to safeguard against CACHE_SECONDS being set externally
delete process.env.CACHE_SECONDS;
});
describe("Test /api/pin backend routing", () => {
it("happy path should pass query params and user PAT, respond with pin content and persist request", async () => {
mocks.getUserAccessByName.mockResolvedValue({ token: "user-pat" });
mocks.pin.mockResolvedValue({
getUserAccessByNameMock.mockResolvedValue({ token: "user-pat" });
pinMock.mockResolvedValue({
status: "success",
content: "mock-pin-svg",
});
@@ -59,8 +60,8 @@ describe("Test /api/pin backend routing", () => {
await router(req, res);
expect(mocks.getUserAccessByName).toHaveBeenCalledWith("anuraghazra");
expect(mocks.pin).toHaveBeenCalledWith(
expect(getUserAccessByNameMock).toHaveBeenCalledWith("anuraghazra");
expect(pinMock).toHaveBeenCalledWith(
{
username: "anuraghazra",
repo: "convoychat",
@@ -78,6 +79,6 @@ describe("Test /api/pin backend routing", () => {
["Content-Type", "image/svg+xml"],
]);
expect(res.end).toHaveBeenCalledExactlyOnceWith("mock-pin-svg");
expect(mocks.storeRequest).toHaveBeenCalledExactlyOnceWith(req);
expect(storeRequestMock).toHaveBeenCalledExactlyOnceWith(req);
});
});
+20 -1
View File
@@ -1,8 +1,18 @@
// @ts-check
import { logger } from "@stats-organization/github-readme-stats-core";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
afterAll,
afterEach,
beforeAll,
beforeEach,
describe,
expect,
it,
vi,
} from "vitest";
import { data_stats, normalizeSvg } from "../utils.js";
@@ -22,6 +32,11 @@ beforeEach(() => {
mock.onPost("https://api.github.com/graphql").reply(200, data_stats);
});
beforeAll(() => {
vi.spyOn(logger, "log").mockImplementation(() => {});
vi.spyOn(logger, "error").mockImplementation(() => {});
});
afterEach(() => {
mock.reset();
vi.unstubAllEnvs();
@@ -29,6 +44,10 @@ afterEach(() => {
vi.resetModules();
});
afterAll(() => {
vi.restoreAllMocks();
});
describe("Test /api contract", () => {
it("should match the public happy-path response snapshot", async () => {
const { default: router } = await import("../../router.js");
@@ -1,8 +1,18 @@
// @ts-check
import { logger } from "@stats-organization/github-readme-stats-core";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
afterAll,
afterEach,
beforeAll,
beforeEach,
describe,
expect,
it,
vi,
} from "vitest";
import { happy_path_gist_data, normalizeSvg } from "../utils.js";
@@ -24,6 +34,11 @@ beforeEach(() => {
.reply(200, happy_path_gist_data);
});
beforeAll(() => {
vi.spyOn(logger, "log").mockImplementation(() => {});
vi.spyOn(logger, "error").mockImplementation(() => {});
});
afterEach(() => {
mock.reset();
vi.unstubAllEnvs();
@@ -31,6 +46,10 @@ afterEach(() => {
vi.resetModules();
});
afterAll(() => {
vi.restoreAllMocks();
});
describe("Test /api/gist contract", () => {
it("should match the public happy-path response snapshot", async () => {
const { default: router } = await import("../../router.js");
+20 -1
View File
@@ -1,8 +1,18 @@
// @ts-check
import { logger } from "@stats-organization/github-readme-stats-core";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
afterAll,
afterEach,
beforeAll,
beforeEach,
describe,
expect,
it,
vi,
} from "vitest";
import { data_user, normalizeSvg } from "../utils.js";
@@ -22,6 +32,11 @@ beforeEach(() => {
mock.onPost("https://api.github.com/graphql").reply(200, data_user);
});
beforeAll(() => {
vi.spyOn(logger, "log").mockImplementation(() => {});
vi.spyOn(logger, "error").mockImplementation(() => {});
});
afterEach(() => {
mock.reset();
vi.unstubAllEnvs();
@@ -29,6 +44,10 @@ afterEach(() => {
vi.resetModules();
});
afterAll(() => {
vi.restoreAllMocks();
});
describe("Test /api/pin contract", () => {
it("should match the public happy-path response snapshot", async () => {
const { default: router } = await import("../../router.js");
@@ -1,8 +1,18 @@
// @ts-check
import { logger } from "@stats-organization/github-readme-stats-core";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
afterAll,
afterEach,
beforeAll,
beforeEach,
describe,
expect,
it,
vi,
} from "vitest";
import { data_langs, normalizeSvg } from "../utils.js";
@@ -22,6 +32,11 @@ beforeEach(() => {
mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
});
beforeAll(() => {
vi.spyOn(logger, "log").mockImplementation(() => {});
vi.spyOn(logger, "error").mockImplementation(() => {});
});
afterEach(() => {
mock.reset();
vi.unstubAllEnvs();
@@ -29,6 +44,10 @@ afterEach(() => {
vi.resetModules();
});
afterAll(() => {
vi.restoreAllMocks();
});
describe("Test /api/top-langs contract", () => {
it("should match the public happy-path response snapshot", async () => {
const { default: router } = await import("../../router.js");
@@ -1,8 +1,18 @@
// @ts-check
import { logger } from "@stats-organization/github-readme-stats-core";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
afterAll,
afterEach,
beforeAll,
beforeEach,
describe,
expect,
it,
vi,
} from "vitest";
import { normalizeSvg, wakaTimeData } from "../utils.js";
@@ -34,6 +44,11 @@ beforeEach(() => {
.reply(200, wakaTimeData);
});
beforeAll(() => {
vi.spyOn(logger, "log").mockImplementation(() => {});
vi.spyOn(logger, "error").mockImplementation(() => {});
});
afterEach(() => {
mock.reset();
vi.unstubAllEnvs();
@@ -41,6 +56,10 @@ afterEach(() => {
vi.resetModules();
});
afterAll(() => {
vi.restoreAllMocks();
});
describe("Test /api/wakatime contract", () => {
it("should match the public happy-path response snapshot", async () => {
const { default: router } = await import("../../router.js");
+19 -1
View File
@@ -2,9 +2,18 @@
* @file Tests for the status/up cloud function.
*/
import { logger } from "@stats-organization/github-readme-stats-core";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
afterAll,
afterEach,
beforeAll,
describe,
expect,
it,
vi,
} from "vitest";
import up, { RATE_LIMIT_SECONDS } from "../api-renamed/status/up.js";
@@ -55,10 +64,19 @@ const shields_down = {
color: "red",
};
beforeAll(() => {
vi.spyOn(logger, "log").mockImplementation(() => {});
vi.spyOn(logger, "error").mockImplementation(() => {});
});
afterEach(() => {
mock.reset();
});
afterAll(() => {
vi.restoreAllMocks();
});
describe("Test /api/status/up", () => {
it("should return `true` if request was successful", async () => {
mock.onPost("https://api.github.com/graphql").replyOnce(200, successData);
+23 -22
View File
@@ -1,25 +1,26 @@
// @ts-check
import { topLangs } from "@stats-organization/github-readme-stats-core";
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
topLangs: vi.fn(),
import router from "../router.js";
import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
import { getUserAccessByName, storeRequest } from "../src/common/database.js";
vi.mock(import("@stats-organization/github-readme-stats-core"), async () => {
const { mockCore } = await import("./utils.js");
return mockCore();
});
vi.mock(import("../src/common/database.js"), async (importOriginal) => ({
...(await importOriginal()),
storeRequest: vi.fn(),
getUserAccessByName: vi.fn(),
}));
vi.mock("@stats-organization/github-readme-stats-core", async () => {
const { mockCore } = await import("./utils.js");
return mockCore({ topLangs: mocks.topLangs });
});
vi.mock("../src/common/database.js", () => ({
storeRequest: mocks.storeRequest,
getUserAccessByName: mocks.getUserAccessByName,
}));
import router from "../router.js";
import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
const topLangsMock = vi.mocked(topLangs);
const storeRequestMock = vi.mocked(storeRequest);
const getUserAccessByNameMock = vi.mocked(getUserAccessByName);
const createRequest = (search = "") => ({
headers: {},
@@ -37,17 +38,17 @@ const defaultCacheHeader =
`stale-while-revalidate=${DURATIONS.ONE_DAY}`;
beforeEach(() => {
mocks.topLangs.mockReset();
mocks.storeRequest.mockReset().mockResolvedValue(undefined);
mocks.getUserAccessByName.mockReset().mockResolvedValue(null);
topLangsMock.mockReset();
storeRequestMock.mockReset().mockResolvedValue(undefined);
getUserAccessByNameMock.mockReset().mockResolvedValue(null);
// CACHE_SECONDS is not set here, this is just to safeguard against CACHE_SECONDS being set externally
delete process.env.CACHE_SECONDS;
});
describe("Test /api/top-langs backend routing", () => {
it("happy path should pass query params and user PAT, respond with top languages content and persist request", async () => {
mocks.getUserAccessByName.mockResolvedValue({ token: "user-pat" });
mocks.topLangs.mockResolvedValue({
getUserAccessByNameMock.mockResolvedValue({ token: "user-pat" });
topLangsMock.mockResolvedValue({
status: "success",
content: "mock-top-langs-svg",
});
@@ -59,8 +60,8 @@ describe("Test /api/top-langs backend routing", () => {
await router(req, res);
expect(mocks.getUserAccessByName).toHaveBeenCalledWith("anuraghazra");
expect(mocks.topLangs).toHaveBeenCalledWith(
expect(getUserAccessByNameMock).toHaveBeenCalledWith("anuraghazra");
expect(topLangsMock).toHaveBeenCalledWith(
{
username: "anuraghazra",
layout: "compact",
@@ -78,6 +79,6 @@ describe("Test /api/top-langs backend routing", () => {
["Content-Type", "image/svg+xml"],
]);
expect(res.end).toHaveBeenCalledExactlyOnceWith("mock-top-langs-svg");
expect(mocks.storeRequest).toHaveBeenCalledExactlyOnceWith(req);
expect(storeRequestMock).toHaveBeenCalledExactlyOnceWith(req);
});
});
+22 -11
View File
@@ -1,5 +1,7 @@
// @ts-check
import { vi } from "vitest";
export const data_stats = {
data: {
user: {
@@ -257,23 +259,32 @@ export const wakaTimeData = {
},
};
/** @typedef {import('@stats-organization/github-readme-stats-core')} CoreModule */
/**
* Creates a mock module for @stats-organization/github-readme-stats-core.
* @param {any} mocks Mocked functions of the core module.
* @returns {any} Mocked core module.
* @returns {CoreModule} Mocked core module.
*/
export function mockCore(mocks) {
const noop = () => undefined;
export function mockCore() {
return {
api: mocks.api ?? noop,
gist: mocks.gist ?? noop,
pin: mocks.pin ?? noop,
topLangs: mocks.topLangs ?? noop,
wakatime: mocks.wakatime ?? noop,
getConfig: mocks.getConfig ?? (() => mocks.config ?? {}),
// @ts-expect-error no need to mock themes at the moment
themes: {},
request: vi.fn(),
fetchWakatimeStats: vi.fn(),
retryer: vi.fn(),
dateDiff: vi.fn(),
api: vi.fn(),
gist: vi.fn(),
pin: vi.fn(),
topLangs: vi.fn(),
wakatime: vi.fn(),
getConfig: vi.fn().mockReturnValue({}),
renderError: ({ message }) => `render-error:${message}`,
clampValue: (value, min, max) => Math.min(Math.max(value, min), max),
logger: {
log: vi.fn(),
error: vi.fn(),
},
};
}
+22 -21
View File
@@ -1,25 +1,26 @@
// @ts-check
import { wakatime } from "@stats-organization/github-readme-stats-core";
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
wakatime: vi.fn(),
import router from "../router.js";
import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
import { getUserAccessByName, storeRequest } from "../src/common/database.js";
vi.mock(import("@stats-organization/github-readme-stats-core"), async () => {
const { mockCore } = await import("./utils.js");
return mockCore();
});
vi.mock(import("../src/common/database.js"), async (importOriginal) => ({
...(await importOriginal()),
storeRequest: vi.fn(),
getUserAccessByName: vi.fn(),
}));
vi.mock("@stats-organization/github-readme-stats-core", async () => {
const { mockCore } = await import("./utils.js");
return mockCore({ wakatime: mocks.wakatime });
});
vi.mock("../src/common/database.js", () => ({
storeRequest: mocks.storeRequest,
getUserAccessByName: mocks.getUserAccessByName,
}));
import router from "../router.js";
import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
const wakatimeMock = vi.mocked(wakatime);
const storeRequestMock = vi.mocked(storeRequest);
const getUserAccessByNameMock = vi.mocked(getUserAccessByName);
const createRequest = (search = "") => ({
headers: {},
@@ -37,16 +38,16 @@ const defaultCacheHeader =
`stale-while-revalidate=${DURATIONS.ONE_DAY}`;
beforeEach(() => {
mocks.wakatime.mockReset();
mocks.storeRequest.mockReset().mockResolvedValue(undefined);
mocks.getUserAccessByName.mockReset().mockResolvedValue(null);
wakatimeMock.mockReset();
storeRequestMock.mockReset().mockResolvedValue(undefined);
getUserAccessByNameMock.mockReset().mockResolvedValue(null);
// CACHE_SECONDS is not set here, this is just to safeguard against CACHE_SECONDS being set externally
delete process.env.CACHE_SECONDS;
});
describe("Test /api/wakatime backend routing", () => {
it("happy path should pass query params, respond with wakatime content and persist request", async () => {
mocks.wakatime.mockResolvedValue({
wakatimeMock.mockResolvedValue({
status: "success",
content: "mock-wakatime-svg",
});
@@ -56,12 +57,12 @@ describe("Test /api/wakatime backend routing", () => {
await router(req, res);
expect(mocks.wakatime).toHaveBeenCalledWith({
expect(wakatimeMock).toHaveBeenCalledWith({
username: "anuraghazra",
theme: "dark",
layout: "compact",
});
expect(mocks.getUserAccessByName).not.toHaveBeenCalled();
expect(getUserAccessByNameMock).not.toHaveBeenCalled();
expect(req.query).toEqual({
username: "anuraghazra",
theme: "dark",
@@ -72,6 +73,6 @@ describe("Test /api/wakatime backend routing", () => {
["Content-Type", "image/svg+xml"],
]);
expect(res.end).toHaveBeenCalledExactlyOnceWith("mock-wakatime-svg");
expect(mocks.storeRequest).toHaveBeenCalledExactlyOnceWith(req);
expect(storeRequestMock).toHaveBeenCalledExactlyOnceWith(req);
});
});