Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more tests to ensure code quality and reliability #59

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
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
15,518 changes: 15,518 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

108 changes: 108 additions & 0 deletions src/app/api/github/actions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { describe, it, expect, vi } from "vitest"
import { storeRepository, getUserRepoStats } from "./actions"
import { getServerOctokit } from "@/lib/github/server"
import { createClient } from "@/lib/supabase/server"
import { revalidatePath } from "next/cache"

vi.mock("@/lib/github/server", () => ({
getServerOctokit: vi.fn(),
}))

vi.mock("@/lib/supabase/server", () => ({
createClient: vi.fn(),
}))

vi.mock("next/cache", () => ({
revalidatePath: vi.fn(),
}))

describe("storeRepository", () => {
it("should store a repository successfully", async () => {
const mockUser = { id: "user-id" }
const mockRepo = {
name: "repo-name",
owner: { login: "repo-owner", type: "User" },
private: false,
stargazers_count: 10,
open_issues_count: 5,
}

createClient.mockReturnValue({
auth: {
getUser: vi.fn().mockResolvedValue({ data: { user: mockUser } }),
},
from: vi.fn().mockReturnThis(),
select: vi.fn().mockReturnThis(),
eq: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnThis(),
maybeSingle: vi.fn().mockResolvedValue({ data: null }),
insert: vi.fn().mockResolvedValue({}),
})

getServerOctokit.mockReturnValue({
rest: {
repos: {
get: vi.fn().mockResolvedValue({ data: mockRepo }),
},
},
})

const result = await storeRepository("repo-owner/repo-name")

expect(result).toEqual({ message: "Repository stored", error: false })
expect(revalidatePath).toHaveBeenCalledWith("/", "layout")
})

it("should return an error if the user is not found", async () => {
createClient.mockReturnValue({
auth: {
getUser: vi.fn().mockResolvedValue({ data: { user: null } }),
},
})

const result = await storeRepository("repo-owner/repo-name")

expect(result).toEqual({ message: "User not found", error: true })
})

it("should return an error if the repository already exists", async () => {
const mockUser = { id: "user-id" }
const mockExistingRepo = { id: "existing-repo-id" }

createClient.mockReturnValue({
auth: {
getUser: vi.fn().mockResolvedValue({ data: { user: mockUser } }),
},
from: vi.fn().mockReturnThis(),
select: vi.fn().mockReturnThis(),
eq: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnThis(),
maybeSingle: vi.fn().mockResolvedValue({ data: mockExistingRepo }),
})

const result = await storeRepository("repo-owner/repo-name")

expect(result).toEqual({ message: "Repository already exists", error: true })
})
})

describe("getUserRepoStats", () => {
it("should return user repository stats", async () => {
const mockOctokit = {
rest: {
repos: {
listCommits: vi.fn().mockResolvedValue({ data: [] }),
},
},
}

const result = await getUserRepoStats(
mockOctokit,
"user-login",
"repo-owner",
"repo-name",
)

expect(result).toEqual({ data: [] })
})
})
118 changes: 118 additions & 0 deletions src/app/auth/actions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { describe, it, expect, vi } from "vitest"
import { login, logout } from "./actions"
import { createClient } from "@/lib/supabase/server"
import { revalidatePath } from "next/cache"
import { redirect } from "next/navigation"
import { headers, cookies } from "next/headers"
import {
GITHUB_REFRESH_TOKEN_COOKIE,
GITHUB_ACCESS_TOKEN_COOKIE,
} from "@/lib/supabase/cookies"

vi.mock("@/lib/supabase/server", () => ({
createClient: vi.fn(),
}))

vi.mock("next/cache", () => ({
revalidatePath: vi.fn(),
}))

vi.mock("next/navigation", () => ({
redirect: vi.fn(),
}))

vi.mock("next/headers", () => ({
headers: vi.fn(),
cookies: vi.fn(),
}))

describe("login", () => {
it("should redirect to GitHub OAuth URL", async () => {
const mockOrigin = "http://localhost:3000"
const mockData = { url: "https://github.com/login/oauth/authorize" }

headers.mockReturnValue({
get: vi.fn().mockReturnValue(mockOrigin),
})

createClient.mockReturnValue({
auth: {
signInWithOAuth: vi.fn().mockResolvedValue({ error: null, data: mockData }),
},
})

await login()

expect(revalidatePath).toHaveBeenCalledWith("/", "layout")
expect(redirect).toHaveBeenCalledWith(mockData.url)
})

it("should redirect to error page if there is an error", async () => {
const mockOrigin = "http://localhost:3000"
const mockError = new Error("OAuth error")

headers.mockReturnValue({
get: vi.fn().mockReturnValue(mockOrigin),
})

createClient.mockReturnValue({
auth: {
signInWithOAuth: vi.fn().mockResolvedValue({ error: mockError, data: null }),
},
})

await login()

expect(redirect).toHaveBeenCalledWith("/error")
})
})

describe("logout", () => {
it("should sign out the user and clear cookies", async () => {
const mockUser = { id: "user-id" }

createClient.mockReturnValue({
auth: {
getUser: vi.fn().mockResolvedValue({ data: { user: mockUser } }),
signOut: vi.fn().mockResolvedValue({}),
},
})

const mockCookies = {
delete: vi.fn(),
}

cookies.mockReturnValue(mockCookies)

await logout()

expect(createClient().auth.signOut).toHaveBeenCalled()
expect(mockCookies.delete).toHaveBeenCalledWith(GITHUB_ACCESS_TOKEN_COOKIE)
expect(mockCookies.delete).toHaveBeenCalledWith(GITHUB_REFRESH_TOKEN_COOKIE)
expect(revalidatePath).toHaveBeenCalledWith("/", "layout")
expect(redirect).toHaveBeenCalledWith("/")
})

it("should clear cookies even if user is not found", async () => {
createClient.mockReturnValue({
auth: {
getUser: vi.fn().mockResolvedValue({ data: { user: null } }),
signOut: vi.fn().mockResolvedValue({}),
},
})

const mockCookies = {
delete: vi.fn(),
}

cookies.mockReturnValue(mockCookies)

await logout()

expect(createClient().auth.signOut).not.toHaveBeenCalled()
expect(mockCookies.delete).toHaveBeenCalledWith(GITHUB_ACCESS_TOKEN_COOKIE)
expect(mockCookies.delete).toHaveBeenCalledWith(GITHUB_REFRESH_TOKEN_COOKIE)
expect(revalidatePath).toHaveBeenCalledWith("/", "layout")
expect(redirect).toHaveBeenCalledWith("/")
})
})
81 changes: 81 additions & 0 deletions src/app/auth/callback/route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { describe, it, expect, vi } from "vitest"
import { GET } from "./route"
import { createClient } from "@/lib/supabase/server"
import { cookies } from "next/headers"
import { NextResponse } from "next/server"

vi.mock("@/lib/supabase/server", () => ({
createClient: vi.fn(),
}))

vi.mock("next/headers", () => ({
cookies: vi.fn(),
}))

vi.mock("next/server", () => ({
NextResponse: {
redirect: vi.fn(),
},
}))

describe("GET", () => {
it("should redirect to the next URL if code is valid", async () => {
const mockRequest = {
url: "http://localhost:3000/auth/callback?code=valid-code&next=/dashboard",
}

const mockSupabase = {
auth: {
exchangeCodeForSession: vi.fn().mockResolvedValue({ error: null }),
onAuthStateChange: vi.fn(),
},
}

const mockCookies = {
set: vi.fn(),
delete: vi.fn(),
}

createClient.mockReturnValue(mockSupabase)
cookies.mockReturnValue(mockCookies)

await GET(mockRequest)

expect(mockSupabase.auth.exchangeCodeForSession).toHaveBeenCalledWith(
"valid-code",
)
expect(NextResponse.redirect).toHaveBeenCalledWith(
"http://localhost:3000/dashboard",
)
})

it("should redirect to error page if code is invalid", async () => {
const mockRequest = {
url: "http://localhost:3000/auth/callback?code=invalid-code",
}

const mockSupabase = {
auth: {
exchangeCodeForSession: vi.fn().mockResolvedValue({ error: new Error("Invalid code") }),
onAuthStateChange: vi.fn(),
},
}

const mockCookies = {
set: vi.fn(),
delete: vi.fn(),
}

createClient.mockReturnValue(mockSupabase)
cookies.mockReturnValue(mockCookies)

await GET(mockRequest)

expect(mockSupabase.auth.exchangeCodeForSession).toHaveBeenCalledWith(
"invalid-code",
)
expect(NextResponse.redirect).toHaveBeenCalledWith(
"http://localhost:3000/auth/auth-code-error",
)
})
})
35 changes: 35 additions & 0 deletions src/app/auth/signout/route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { describe, it, expect, vi } from "vitest"
import { GET } from "./route"
import { createClient } from "@/lib/supabase/server"
import { NextResponse } from "next/server"

vi.mock("@/lib/supabase/server", () => ({
createClient: vi.fn(),
}))

vi.mock("next/server", () => ({
NextResponse: {
redirect: vi.fn(),
},
}))

describe("GET", () => {
it("should sign out the user and redirect to the origin", async () => {
const mockRequest = {
url: "http://localhost:3000/auth/signout",
}

const mockSupabase = {
auth: {
signOut: vi.fn().mockResolvedValue({}),
},
}

createClient.mockReturnValue(mockSupabase)

await GET(mockRequest)

expect(mockSupabase.auth.signOut).toHaveBeenCalled()
expect(NextResponse.redirect).toHaveBeenCalledWith("http://localhost:3000")
})
})
Loading
Loading