Back to Repositories

Testing GitHub API Status Response Handling in github-readme-stats

This test suite validates the GitHub API status endpoint functionality in the github-readme-stats repository, focusing on response handling and error management for different API states. The tests ensure proper handling of rate limiting, authentication, and various response formats.

Test Coverage Overview

Comprehensive test coverage for the status/up cloud function, examining API response scenarios and error conditions.

Key areas tested include:
  • Successful API responses
  • Rate limiting scenarios
  • Authentication failures
  • Multiple response formats (JSON, shields.io)
  • Cache header management

Implementation Analysis

The testing approach utilizes Jest’s mocking capabilities with axios-mock-adapter to simulate GitHub API responses. The implementation follows a request-response pattern, testing various query parameters and response formats.

Framework-specific features leveraged include:
  • Jest’s mock functions for response validation
  • Async/await test patterns
  • Mock adapter for HTTP request simulation
  • Modular test case organization

Technical Details

Testing tools and configuration:
  • Jest test framework
  • axios-mock-adapter for API mocking
  • Mock response data structures for various scenarios
  • Custom faker utility for request/response generation
  • Automatic mock reset after each test

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through thorough error handling and edge case coverage.

Notable practices include:
  • Isolated test cases with clear assertions
  • Comprehensive mock reset between tests
  • Consistent error scenario coverage
  • Proper header validation
  • Structured test organization with descriptive naming

anuraghazra/github-readme-stats

tests/status.up.test.js

            
/**
 * @file Tests for the status/up cloud function.
 */
import { jest } from "@jest/globals";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import up, { RATE_LIMIT_SECONDS } from "../api/status/up.js";
import { expect, it, describe, afterEach } from "@jest/globals";

const mock = new MockAdapter(axios);

const successData = {
  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",
    },
  ],
};

const bad_credentials_error = {
  message: "Bad credentials",
};

const shields_up = {
  schemaVersion: 1,
  label: "Public Instance",
  isError: true,
  message: "up",
  color: "brightgreen",
};
const shields_down = {
  schemaVersion: 1,
  label: "Public Instance",
  isError: true,
  message: "down",
  color: "red",
};

afterEach(() => {
  mock.reset();
});

describe("Test /api/status/up", () => {
  it("should return `true` if request was successful", async () => {
    mock.onPost("https://api.github.com/graphql").replyOnce(200, successData);

    const { req, res } = faker({}, {});
    await up(req, res);

    expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
    expect(res.send).toBeCalledWith(true);
  });

  it("should return `false` if all PATs are rate limited", async () => {
    mock.onPost("https://api.github.com/graphql").reply(200, rate_limit_error);

    const { req, res } = faker({}, {});
    await up(req, res);

    expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
    expect(res.send).toBeCalledWith(false);
  });

  it("should return JSON `true` if request was successful and type='json'", async () => {
    mock.onPost("https://api.github.com/graphql").replyOnce(200, successData);

    const { req, res } = faker({ type: "json" }, {});
    await up(req, res);

    expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
    expect(res.send).toBeCalledWith({ up: true });
  });

  it("should return JSON `false` if all PATs are rate limited and type='json'", async () => {
    mock.onPost("https://api.github.com/graphql").reply(200, rate_limit_error);

    const { req, res } = faker({ type: "json" }, {});
    await up(req, res);

    expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
    expect(res.send).toBeCalledWith({ up: false });
  });

  it("should return UP shields.io config if request was successful and type='shields'", async () => {
    mock.onPost("https://api.github.com/graphql").replyOnce(200, successData);

    const { req, res } = faker({ type: "shields" }, {});
    await up(req, res);

    expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
    expect(res.send).toBeCalledWith(shields_up);
  });

  it("should return DOWN shields.io config if all PATs are rate limited and type='shields'", async () => {
    mock.onPost("https://api.github.com/graphql").reply(200, rate_limit_error);

    const { req, res } = faker({ type: "shields" }, {});
    await up(req, res);

    expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
    expect(res.send).toBeCalledWith(shields_down);
  });

  it("should return `true` if the first PAT is rate limited but the second PATs works", async () => {
    mock
      .onPost("https://api.github.com/graphql")
      .replyOnce(200, rate_limit_error)
      .onPost("https://api.github.com/graphql")
      .replyOnce(200, successData);

    const { req, res } = faker({}, {});
    await up(req, res);

    expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
    expect(res.send).toBeCalledWith(true);
  });

  it("should return `true` if the first PAT has 'Bad credentials' but the second PAT works", async () => {
    mock
      .onPost("https://api.github.com/graphql")
      .replyOnce(404, bad_credentials_error)
      .onPost("https://api.github.com/graphql")
      .replyOnce(200, successData);

    const { req, res } = faker({}, {});
    await up(req, res);

    expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
    expect(res.send).toBeCalledWith(true);
  });

  it("should return `false` if all pats have 'Bad credentials'", async () => {
    mock
      .onPost("https://api.github.com/graphql")
      .reply(404, bad_credentials_error);

    const { req, res } = faker({}, {});
    await up(req, res);

    expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
    expect(res.send).toBeCalledWith(false);
  });

  it("should throw an error if the request fails", async () => {
    mock.onPost("https://api.github.com/graphql").networkError();

    const { req, res } = faker({}, {});
    await up(req, res);

    expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
    expect(res.send).toBeCalledWith(false);
  });

  it("should have proper cache when no error is thrown", async () => {
    mock.onPost("https://api.github.com/graphql").replyOnce(200, successData);

    const { req, res } = faker({}, {});
    await up(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.onPost("https://api.github.com/graphql").networkError();

    const { req, res } = faker({}, {});
    await up(req, res);

    expect(res.setHeader.mock.calls).toEqual([
      ["Content-Type", "application/json"],
      ["Cache-Control", "no-store"],
    ]);
  });
});