/** * @file Tests for the status/pat-info cloud function. */ import dotenv from "dotenv"; dotenv.config(); import { jest } from "@jest/globals"; import axios from "axios"; import MockAdapter from "axios-mock-adapter"; import patInfo, { RATE_LIMIT_SECONDS } from "../api/status/pat-info.js"; import { expect, it, describe, afterEach, beforeAll } from "@jest/globals"; const mock = new MockAdapter(axios); const successData = { data: { rateLimit: { remaining: 4986, }, }, }; const faker = (query) => { const req = { query: { ...query }, }; const res = { setHeader: jest.fn(), send: jest.fn(), }; return { req, res }; }; const rate_limit_error = { errors: [ { type: "RATE_LIMITED", message: "API rate limit exceeded for user ID.", }, ], data: { rateLimit: { resetAt: Date.now(), }, }, }; const other_error = { errors: [ { type: "SOME_ERROR", message: "This is a error", }, ], }; const bad_credentials_error = { message: "Bad credentials", }; afterEach(() => { mock.reset(); }); describe("Test /api/status/pat-info", () => { beforeAll(() => { // reset patenv first so that dotenv doesn't populate them with local envs process.env = {}; process.env.PAT_1 = "testPAT1"; process.env.PAT_2 = "testPAT2"; process.env.PAT_3 = "testPAT3"; process.env.PAT_4 = "testPAT4"; }); it("should return only 'validPATs' if all PATs are valid", async () => { mock .onPost("https://api.github.com/graphql") .replyOnce(200, rate_limit_error) .onPost("https://api.github.com/graphql") .reply(200, successData); const { req, res } = faker({}, {}); await patInfo(req, res); expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); expect(res.send).toBeCalledWith( JSON.stringify( { validPATs: ["PAT_2", "PAT_3", "PAT_4"], expiredPATs: [], exhaustedPATs: ["PAT_1"], suspendedPATs: [], errorPATs: [], details: { PAT_1: { status: "exhausted", remaining: 0, resetIn: "0 minutes", }, PAT_2: { status: "valid", remaining: 4986, }, PAT_3: { status: "valid", remaining: 4986, }, PAT_4: { status: "valid", remaining: 4986, }, }, }, null, 2, ), ); }); it("should return `errorPATs` if a PAT causes an error to be thrown", async () => { mock .onPost("https://api.github.com/graphql") .replyOnce(200, other_error) .onPost("https://api.github.com/graphql") .reply(200, successData); const { req, res } = faker({}, {}); await patInfo(req, res); expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); expect(res.send).toBeCalledWith( JSON.stringify( { validPATs: ["PAT_2", "PAT_3", "PAT_4"], expiredPATs: [], exhaustedPATs: [], suspendedPATs: [], errorPATs: ["PAT_1"], details: { PAT_1: { status: "error", error: { type: "SOME_ERROR", message: "This is a error", }, }, PAT_2: { status: "valid", remaining: 4986, }, PAT_3: { status: "valid", remaining: 4986, }, PAT_4: { status: "valid", remaining: 4986, }, }, }, null, 2, ), ); }); it("should return `expiredPaths` if a PAT returns a 'Bad credentials' error", async () => { mock .onPost("https://api.github.com/graphql") .replyOnce(404, bad_credentials_error) .onPost("https://api.github.com/graphql") .reply(200, successData); const { req, res } = faker({}, {}); await patInfo(req, res); expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); expect(res.send).toBeCalledWith( JSON.stringify( { validPATs: ["PAT_2", "PAT_3", "PAT_4"], expiredPATs: ["PAT_1"], exhaustedPATs: [], suspendedPATs: [], errorPATs: [], details: { PAT_1: { status: "expired", }, PAT_2: { status: "valid", remaining: 4986, }, PAT_3: { status: "valid", remaining: 4986, }, PAT_4: { status: "valid", remaining: 4986, }, }, }, null, 2, ), ); }); it("should throw an error if something goes wrong", async () => { mock.onPost("https://api.github.com/graphql").networkError(); const { req, res } = faker({}, {}); await patInfo(req, res); expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); expect(res.send).toBeCalledWith("Something went wrong: Network Error"); }); it("should have proper cache when no error is thrown", async () => { mock.onPost("https://api.github.com/graphql").reply(200, successData); const { req, res } = faker({}, {}); await patInfo(req, res); expect(res.setHeader.mock.calls).toEqual([ ["Content-Type", "application/json"], ["Cache-Control", `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`], ]); }); it("should have proper cache when error is thrown", async () => { mock.reset(); mock.onPost("https://api.github.com/graphql").networkError(); const { req, res } = faker({}, {}); await patInfo(req, res); expect(res.setHeader.mock.calls).toEqual([ ["Content-Type", "application/json"], ["Cache-Control", "no-store"], ]); }); });