import { jest } from "@jest/globals"; import axios from "axios"; import MockAdapter from "axios-mock-adapter"; import api from "../api/index.js"; import { calculateRank } from "../src/calculateRank.js"; import { renderStatsCard } from "../src/cards/stats-card.js"; import { CONSTANTS, renderError } from "../src/common/utils.js"; import { expect, it, describe, afterEach } from "@jest/globals"; const stats = { name: "Anurag Hazra", totalStars: 100, totalCommits: 200, totalIssues: 300, totalPRs: 400, totalPRsMerged: 320, mergedPRsPercentage: 80, totalReviews: 50, totalDiscussionsStarted: 10, totalDiscussionsAnswered: 40, contributedTo: 50, rank: null, }; stats.rank = calculateRank({ all_commits: false, commits: stats.totalCommits, prs: stats.totalPRs, reviews: stats.totalReviews, issues: stats.totalIssues, repos: 1, stars: stats.totalStars, followers: 0, }); const data_stats = { data: { user: { name: stats.name, repositoriesContributedTo: { totalCount: stats.contributedTo }, contributionsCollection: { totalCommitContributions: stats.totalCommits, totalPullRequestReviewContributions: stats.totalReviews, }, pullRequests: { totalCount: stats.totalPRs }, mergedPullRequests: { totalCount: stats.totalPRsMerged }, openIssues: { totalCount: stats.totalIssues }, closedIssues: { totalCount: 0 }, followers: { totalCount: 0 }, repositoryDiscussions: { totalCount: stats.totalDiscussionsStarted }, repositoryDiscussionComments: { totalCount: stats.totalDiscussionsAnswered, }, repositories: { totalCount: 1, nodes: [{ stargazers: { totalCount: 100 } }], pageInfo: { hasNextPage: false, endCursor: "cursor", }, }, }, }, }; const error = { errors: [ { type: "NOT_FOUND", path: ["user"], locations: [], message: "Could not fetch user", }, ], }; const mock = new MockAdapter(axios); const faker = (query, data) => { const req = { query: { username: "anuraghazra", ...query, }, }; const res = { setHeader: jest.fn(), send: jest.fn(), }; mock.onPost("https://api.github.com/graphql").replyOnce(200, data); return { req, res }; }; afterEach(() => { mock.reset(); }); describe("Test /api/", () => { it("should test the request", async () => { const { req, res } = faker({}, data_stats); await api(req, res); expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); expect(res.send).toBeCalledWith(renderStatsCard(stats, { ...req.query })); }); it("should render error card on error", async () => { const { req, res } = faker({}, error); await api(req, res); expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); expect(res.send).toBeCalledWith( renderError( error.errors[0].message, "Make sure the provided username is not an organization", ), ); }); it("should render error card in same theme as requested card", async () => { const { req, res } = faker({ theme: "merko" }, error); await api(req, res); expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); expect(res.send).toBeCalledWith( renderError( error.errors[0].message, "Make sure the provided username is not an organization", { theme: "merko" }, ), ); }); it("should get the query options", async () => { const { req, res } = faker( { username: "anuraghazra", hide: "issues,prs,contribs", show_icons: true, hide_border: true, line_height: 100, title_color: "fff", icon_color: "fff", text_color: "fff", bg_color: "fff", }, data_stats, ); await api(req, res); expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); expect(res.send).toBeCalledWith( renderStatsCard(stats, { hide: ["issues", "prs", "contribs"], show_icons: true, hide_border: true, line_height: 100, title_color: "fff", icon_color: "fff", text_color: "fff", bg_color: "fff", }), ); }); it("should set shorter cache when error", async () => { const { req, res } = faker({}, error); await api(req, res); expect(res.setHeader.mock.calls).toEqual([ ["Content-Type", "image/svg+xml"], [ "Cache-Control", `max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${ CONSTANTS.ERROR_CACHE_SECONDS }, stale-while-revalidate=${CONSTANTS.ONE_DAY}`, ], ]); }); it("should allow changing ring_color", async () => { const { req, res } = faker( { username: "anuraghazra", hide: "issues,prs,contribs", show_icons: true, hide_border: true, line_height: 100, title_color: "fff", ring_color: "0000ff", icon_color: "fff", text_color: "fff", bg_color: "fff", }, data_stats, ); await api(req, res); expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); expect(res.send).toBeCalledWith( renderStatsCard(stats, { hide: ["issues", "prs", "contribs"], show_icons: true, hide_border: true, line_height: 100, title_color: "fff", ring_color: "0000ff", icon_color: "fff", text_color: "fff", bg_color: "fff", }), ); }); it("should render error card if username in blacklist", async () => { const { req, res } = faker({ username: "renovate-bot" }, data_stats); await api(req, res); expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); expect(res.send).toBeCalledWith( renderError("Something went wrong", "This username is blacklisted"), ); }); it("should render error card when wrong locale is provided", async () => { const { req, res } = faker({ locale: "asdf" }, data_stats); await api(req, res); expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); expect(res.send).toBeCalledWith( renderError("Something went wrong", "Language not found"), ); }); it("should render error card when include_all_commits true and upstream API fails", async () => { mock .onGet("https://api.github.com/search/commits?q=author:anuraghazra") .reply(200, { error: "Some test error message" }); const { req, res } = faker( { username: "anuraghazra", include_all_commits: true }, data_stats, ); await api(req, res); expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); expect(res.send).toBeCalledWith( renderError("Could not fetch total commits.", "Please try again later"), ); // Received SVG output should not contain string "https://tiny.one/readme-stats" expect(res.send.mock.calls[0][0]).not.toContain( "https://tiny.one/readme-stats", ); }); });