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:
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user