Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions src/app/api/dashboard/stats/route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { describe, expect, it, vi, beforeEach } from "vitest";
import { NextRequest } from "next/server";
import { GET } from "./route";
import { getServerSession } from "next-auth";
import { fetchViewerLogin } from "@/lib/githubViewer";
import { fetchCommitActivityHeatmap } from "@/lib/githubYearInReview";

// Mock dependencies
vi.mock("next-auth", () => ({
getServerSession: vi.fn(),
}));

vi.mock("@/lib/githubViewer", () => ({
fetchViewerLogin: vi.fn(),
}));

vi.mock("@/lib/githubYearInReview", () => ({
fetchCommitActivityHeatmap: vi.fn(),
}));

describe("GET /api/dashboard/stats", () => {
beforeEach(() => {
vi.resetAllMocks();
});
Comment on lines +22 to +24

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve maintainability and reduce code duplication, you can define the common session mock objects as constants within the describe block. This will make the tests cleaner, more readable, and easier to maintain.

Suggested change
beforeEach(() => {
vi.resetAllMocks();
});
const mockSessionWithToken = {
user: { name: "Test User" },
accessToken: "mock-token",
expires: "1",
};
const mockSessionWithLogin = {
user: { login: "testuser" },
accessToken: "mock-token",
expires: "1",
};
beforeEach(() => {
vi.resetAllMocks();
});


const createRequest = (url: string) => new NextRequest(new URL(url));

it("should return 401 if unauthorized (no session)", async () => {
vi.mocked(getServerSession).mockResolvedValueOnce(null);

const req = createRequest("http://localhost/api/dashboard/stats");
const res = await GET(req);

expect(res.status).toBe(401);
expect(await res.json()).toEqual({ error: "Unauthorized" });
});

it("should return 401 if unauthorized (no token)", async () => {
vi.mocked(getServerSession).mockResolvedValueOnce({
user: { name: "Test User" },
expires: "1",
});

const req = createRequest("http://localhost/api/dashboard/stats");
const res = await GET(req);

expect(res.status).toBe(401);
expect(await res.json()).toEqual({ error: "Unauthorized" });
});

it("should return 400 for invalid year (too old)", async () => {
vi.mocked(getServerSession).mockResolvedValueOnce({
user: { name: "Test User" },
accessToken: "mock-token",
expires: "1",
});
Comment on lines +52 to +56

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

With the suggested constants for session mocks defined, you can replace this inline object with mockSessionWithToken. This should be applied to all similar cases (e.g., lines 66-70 and 132-136) to improve consistency and reduce repetition.

        vi.mocked(getServerSession).mockResolvedValueOnce(mockSessionWithToken);


const req = createRequest("http://localhost/api/dashboard/stats?year=2000");
const res = await GET(req);

expect(res.status).toBe(400);
expect(await res.json()).toEqual({ error: "Invalid year" });
});

it("should return 400 for invalid year (future)", async () => {
vi.mocked(getServerSession).mockResolvedValueOnce({
user: { name: "Test User" },
accessToken: "mock-token",
expires: "1",
});

const futureYear = new Date().getUTCFullYear() + 1;
const req = createRequest(`http://localhost/api/dashboard/stats?year=${futureYear}`);
const res = await GET(req);

expect(res.status).toBe(400);
expect(await res.json()).toEqual({ error: "Invalid year" });
});

it("should handle error in catch block and return 500", async () => {
vi.mocked(getServerSession).mockResolvedValueOnce({
user: { login: "testuser" },
accessToken: "mock-token",
expires: "1",
});
Comment on lines +81 to +85

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similarly, replace this inline object with the mockSessionWithLogin constant. This should be applied to all similar cases (e.g., lines 98-102 and 114-118) for better code reuse.

        vi.mocked(getServerSession).mockResolvedValueOnce(mockSessionWithLogin);


const errorMessage = "Failed to fetch heatmap";
vi.mocked(fetchCommitActivityHeatmap).mockRejectedValueOnce(new Error(errorMessage));

const req = createRequest("http://localhost/api/dashboard/stats");
const res = await GET(req);

expect(res.status).toBe(500);
expect(await res.json()).toEqual({ error: errorMessage });
});

it("should handle unknown error in catch block and return 500", async () => {
vi.mocked(getServerSession).mockResolvedValueOnce({
user: { login: "testuser" },
accessToken: "mock-token",
expires: "1",
});

vi.mocked(fetchCommitActivityHeatmap).mockRejectedValueOnce("String error, not an Error instance");

const req = createRequest("http://localhost/api/dashboard/stats");
const res = await GET(req);

expect(res.status).toBe(500);
expect(await res.json()).toEqual({ error: "Unknown error" });
});

it("should return 200 and heatmap data on success", async () => {
vi.mocked(getServerSession).mockResolvedValueOnce({
user: { login: "testuser" },
accessToken: "mock-token",
expires: "1",
});

const mockHeatmap: number[][] = [];
vi.mocked(fetchCommitActivityHeatmap).mockResolvedValueOnce(mockHeatmap);

const currentYear = new Date().getUTCFullYear();
const req = createRequest("http://localhost/api/dashboard/stats");
const res = await GET(req);

expect(res.status).toBe(200);
expect(await res.json()).toEqual({ year: currentYear, heatmap: mockHeatmap });
});

it("should use fetchViewerLogin when user.login is missing", async () => {
vi.mocked(getServerSession).mockResolvedValueOnce({
user: { name: "Test User" }, // no login
accessToken: "mock-token",
expires: "1",
});

vi.mocked(fetchViewerLogin).mockResolvedValueOnce("fetcheduser");
const mockHeatmap: number[][] = [];
vi.mocked(fetchCommitActivityHeatmap).mockResolvedValueOnce(mockHeatmap);

const req = createRequest("http://localhost/api/dashboard/stats");
const res = await GET(req);

expect(fetchViewerLogin).toHaveBeenCalledWith("mock-token");
expect(fetchCommitActivityHeatmap).toHaveBeenCalledWith("fetcheduser", expect.any(Number), "mock-token");
expect(res.status).toBe(200);
});
});
Loading