Add some (WIP) tests
This commit is contained in:
parent
b64281b1ac
commit
86e323fbca
15 changed files with 417 additions and 384 deletions
|
@ -2,11 +2,15 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
preset: "ts-jest",
|
preset: "ts-jest",
|
||||||
testEnvironment: "node",
|
testEnvironment: "node",
|
||||||
setupFiles: ["<rootDir>/test/setup-tests.ts"],
|
setupFiles: ["<rootDir>/src/test/setup-tests.ts"],
|
||||||
// TODO: update to app dir
|
// TODO: update to app dir
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
"@lib/(.*)": "<rootDir>/src/lib/$1",
|
"@lib/(.*)": "<rootDir>/src/lib/$1",
|
||||||
"@routes/(.*)": "<rootDir>/src/routes/$1"
|
"@components/(.*)": "<rootDir>/src/app/components/$1",
|
||||||
|
"\\.(css)$": "identity-obj-proxy"
|
||||||
},
|
},
|
||||||
testPathIgnorePatterns: ["<rootDir>/node_modules/", "<rootDir>/dist/"]
|
testPathIgnorePatterns: ["/node_modules/", "/.next/"],
|
||||||
|
transform: {
|
||||||
|
"^.+\\.(js|jsx|ts|tsx)$": "ts-jest"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
"@radix-ui/react-tabs": "^1.0.2",
|
"@radix-ui/react-tabs": "^1.0.2",
|
||||||
"@radix-ui/react-tooltip": "^1.0.4",
|
"@radix-ui/react-tooltip": "^1.0.4",
|
||||||
"@vercel/og": "^0.0.27",
|
"@vercel/og": "^0.0.27",
|
||||||
"@wcj/markdown-to-html": "^2.2.1",
|
|
||||||
"client-only": "^0.0.1",
|
"client-only": "^0.0.1",
|
||||||
"client-zip": "2.3.0",
|
"client-zip": "2.3.0",
|
||||||
"cmdk": "^0.1.22",
|
"cmdk": "^0.1.22",
|
||||||
|
@ -42,12 +41,15 @@
|
||||||
"swr": "^2.0.4",
|
"swr": "^2.0.4",
|
||||||
"textarea-markdown-editor": "1.0.4",
|
"textarea-markdown-editor": "1.0.4",
|
||||||
"ts-jest": "^29.0.5",
|
"ts-jest": "^29.0.5",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0",
|
||||||
|
"zlib": "^1.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@next/bundle-analyzer": "13.1.7-canary.26",
|
"@next/bundle-analyzer": "13.1.7-canary.26",
|
||||||
"@total-typescript/ts-reset": "^0.3.7",
|
"@total-typescript/ts-reset": "^0.3.7",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
|
"@types/git-http-backend": "^1.0.1",
|
||||||
|
"@types/jest": "^29.4.0",
|
||||||
"@types/lodash.debounce": "^4.0.7",
|
"@types/lodash.debounce": "^4.0.7",
|
||||||
"@types/node": "18.11.18",
|
"@types/node": "18.11.18",
|
||||||
"@types/react": "18.0.27",
|
"@types/react": "18.0.27",
|
||||||
|
@ -56,11 +58,14 @@
|
||||||
"@types/uuid": "^9.0.1",
|
"@types/uuid": "^9.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.53.0",
|
"@typescript-eslint/eslint-plugin": "^5.53.0",
|
||||||
"@typescript-eslint/parser": "^5.53.0",
|
"@typescript-eslint/parser": "^5.53.0",
|
||||||
|
"@wcj/markdown-to-html": "^2.2.1",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"csstype": "^3.1.1",
|
"csstype": "^3.1.1",
|
||||||
|
"dotenv": "^16.0.3",
|
||||||
"eslint": "8.33.0",
|
"eslint": "8.33.0",
|
||||||
"eslint-config-next": "13.1.7-canary.26",
|
"eslint-config-next": "13.1.7-canary.26",
|
||||||
|
"jest-mock-extended": "^3.0.2",
|
||||||
"next-unused": "0.0.6",
|
"next-unused": "0.0.6",
|
||||||
"postcss-flexbugs-fixes": "^5.0.2",
|
"postcss-flexbugs-fixes": "^5.0.2",
|
||||||
"postcss-hover-media-feature": "^1.0.2",
|
"postcss-hover-media-feature": "^1.0.2",
|
||||||
|
|
513
pnpm-lock.yaml
513
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -55,7 +55,7 @@ export default function HomePage({
|
||||||
}}
|
}}
|
||||||
icon={<Settings />}
|
icon={<Settings />}
|
||||||
>
|
>
|
||||||
Go to Settings
|
Go to Settings
|
||||||
</Item>
|
</Item>
|
||||||
</Command.Group>
|
</Command.Group>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -7,7 +7,9 @@ import { File } from "react-feather"
|
||||||
import { fetchWithUser } from "src/app/lib/fetch-with-user"
|
import { fetchWithUser } from "src/app/lib/fetch-with-user"
|
||||||
import Item from "../item"
|
import Item from "../item"
|
||||||
|
|
||||||
export default function PostsPage({ setOpen }: {
|
export default function PostsPage({
|
||||||
|
setOpen
|
||||||
|
}: {
|
||||||
setOpen: (open: boolean) => void
|
setOpen: (open: boolean) => void
|
||||||
}) {
|
}) {
|
||||||
const { session } = useSessionSWR()
|
const { session } = useSessionSWR()
|
||||||
|
@ -32,7 +34,14 @@ export default function PostsPage({ setOpen }: {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: 100 }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
height: 100
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
27
src/lib/__tests__/byte-to-mb.test.ts
Normal file
27
src/lib/__tests__/byte-to-mb.test.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import byteToMB from "@lib/byte-to-mb"
|
||||||
|
|
||||||
|
describe("byteToMB", () => {
|
||||||
|
it("converts 0 bytes to 0 MB", () => {
|
||||||
|
expect(byteToMB(0)).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("converts 1024 bytes to 0.001 MB", () => {
|
||||||
|
expect(byteToMB(1024)).toBeCloseTo(0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("converts 1048576 bytes to 1 MB", () => {
|
||||||
|
expect(byteToMB(1048576)).toEqual(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("converts 3145728 bytes to 3 MB", () => {
|
||||||
|
expect(byteToMB(3145728)).toEqual(3)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns NaN when given a negative number", () => {
|
||||||
|
expect(byteToMB(-1)).toBeNaN()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns NaN when given a non-numeric value", () => {
|
||||||
|
expect(byteToMB("test" as any)).toBeNaN()
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,4 +1,9 @@
|
||||||
const byteToMB = (bytes: number) =>
|
const byteToMB = (bytes: number) => {
|
||||||
Math.round((bytes / 1024 / 1024) * 100) / 100
|
if (bytes < 0) {
|
||||||
|
return NaN
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round((bytes / 1024 / 1024) * 100) / 100
|
||||||
|
}
|
||||||
|
|
||||||
export default byteToMB
|
export default byteToMB
|
||||||
|
|
54
src/lib/server/__tests__/verify-api-user.test.ts
Normal file
54
src/lib/server/__tests__/verify-api-user.test.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import { verifyApiUser } from "../verify-api-user"
|
||||||
|
import { prismaMock } from "src/test/prisma.mock"
|
||||||
|
import "src/test/react.mock"
|
||||||
|
import { User } from "@prisma/client"
|
||||||
|
|
||||||
|
describe("verifyApiUser", () => {
|
||||||
|
const mockReq = {} as any
|
||||||
|
const mockRes = {} as any
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns null if there is no userId param or auth token", async () => {
|
||||||
|
mockReq.query = {}
|
||||||
|
mockReq.headers = {}
|
||||||
|
const result = await verifyApiUser(mockReq, mockRes)
|
||||||
|
expect(result).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the user id if there is a userId param and it matches the authenticated user id", async () => {
|
||||||
|
mockReq.query = { userId: "123" }
|
||||||
|
const mockUser = { id: "123" }
|
||||||
|
const mockGetCurrentUser = prismaMock.user.findUnique.mockResolvedValue(
|
||||||
|
mockUser as User
|
||||||
|
)
|
||||||
|
const result = await verifyApiUser(mockReq, mockRes)
|
||||||
|
expect(mockGetCurrentUser).toHaveBeenCalled()
|
||||||
|
expect(result).toEqual("123")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns null if there is a userId param but it doesn't match the authenticated user id", async () => {
|
||||||
|
mockReq.query = { userId: "123" }
|
||||||
|
const mockUser = { id: "456" }
|
||||||
|
const mockGetCurrentUser = jest.fn().mockResolvedValue(mockUser)
|
||||||
|
const result = await verifyApiUser(mockReq, mockRes)
|
||||||
|
expect(mockGetCurrentUser).toHaveBeenCalled()
|
||||||
|
expect(result).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the user id if there is an auth token and it is valid", async () => {
|
||||||
|
mockReq.query = {}
|
||||||
|
mockReq.headers.authorization = "Bearer mytoken"
|
||||||
|
const mockUser = { userId: "123", expiresAt: new Date(Date.now() + 10000) }
|
||||||
|
const mockFindUnique = jest.fn().mockResolvedValue(mockUser)
|
||||||
|
const mockPrisma = { apiToken: { findUnique: mockFindUnique } } as any
|
||||||
|
const result = await verifyApiUser(mockReq, mockRes)
|
||||||
|
expect(mockFindUnique).toHaveBeenCalledWith({
|
||||||
|
where: { token: "mytoken" },
|
||||||
|
select: { userId: true, expiresAt: true }
|
||||||
|
})
|
||||||
|
expect(result).toEqual("123")
|
||||||
|
})
|
||||||
|
})
|
|
@ -3,7 +3,7 @@ import { parseQueryParam } from "@lib/server/parse-query-param"
|
||||||
import { getCurrentUser } from "@lib/server/session"
|
import { getCurrentUser } from "@lib/server/session"
|
||||||
import { NextApiRequest, NextApiResponse } from "next"
|
import { NextApiRequest, NextApiResponse } from "next"
|
||||||
import { prisma } from "src/lib/server/prisma"
|
import { prisma } from "src/lib/server/prisma"
|
||||||
import { deleteUser } from "../user/[id]"
|
import { deleteUser } from "../user/[userId]"
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
"user",
|
"user",
|
||||||
|
|
29
src/test/.env.test
Normal file
29
src/test/.env.test
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
|
||||||
|
|
||||||
|
# Optional if you use Vercel (defaults to VERCEL_URL).
|
||||||
|
# Necessary in development unless you use the vercel CLI (`vc dev`)
|
||||||
|
DRIFT_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# Optional: The first user becomes an admin. Defaults to false
|
||||||
|
ENABLE_ADMIN=false
|
||||||
|
|
||||||
|
# Required: Next auth secret is a required valid JWT secret. You can generate one with `openssl rand -hex 32`
|
||||||
|
NEXTAUTH_SECRET=7f8b8b5c5e5f5e5f5e5f5e5f5e5f5e5f5e5f5e5f5e5f5e5f5e5f5e5f5e5f5f5
|
||||||
|
|
||||||
|
# Required: but unnecessary if you use a supported host like Vercel
|
||||||
|
NEXTAUTH_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# Optional: for locking your instance
|
||||||
|
REGISTRATION_PASSWORD=
|
||||||
|
|
||||||
|
# Optional: for if you want GitHub oauth. Currently incompatible with the registration password
|
||||||
|
GITHUB_CLIENT_ID=
|
||||||
|
GITHUB_CLIENT_SECRET=
|
||||||
|
|
||||||
|
# Optional: if you want to support credential auth (username/password, supports registration password)
|
||||||
|
# Defaults to true
|
||||||
|
CREDENTIAL_AUTH=true
|
||||||
|
|
||||||
|
# Optional:
|
||||||
|
WELCOME_CONTENT=
|
||||||
|
WELCOME_TITLE=
|
16
src/test/prisma.mock.ts
Normal file
16
src/test/prisma.mock.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { PrismaClient } from "@prisma/client"
|
||||||
|
|
||||||
|
import { mockDeep, mockReset, DeepMockProxy } from "jest-mock-extended"
|
||||||
|
|
||||||
|
import { prisma } from "@lib/server/prisma"
|
||||||
|
|
||||||
|
jest.mock("@lib/server/prisma", () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: mockDeep<PrismaClient>()
|
||||||
|
}))
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockReset(prismaMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const prismaMock = prisma as unknown as DeepMockProxy<PrismaClient>
|
12
src/test/react.mock.ts
Normal file
12
src/test/react.mock.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
jest.mock("react", () => {
|
||||||
|
const ActualReact = jest.requireActual("react")
|
||||||
|
|
||||||
|
return {
|
||||||
|
...ActualReact,
|
||||||
|
cache: jest.fn((fn) => {
|
||||||
|
return fn()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export {}
|
4
src/test/setup-tests.ts
Normal file
4
src/test/setup-tests.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import * as dotenv from "dotenv"
|
||||||
|
import * as path from "path"
|
||||||
|
|
||||||
|
dotenv.config({ path: path.resolve(__dirname, "./.env.test") })
|
101
tsconfig.json
101
tsconfig.json
|
@ -1,44 +1,61 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"name": "typescript-plugin-css-modules"
|
"name": "typescript-plugin-css-modules"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "next"
|
"name": "next"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"target": "es2020",
|
"target": "es2020",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": [
|
||||||
"allowJs": true,
|
"dom",
|
||||||
"skipLibCheck": true,
|
"dom.iterable",
|
||||||
"strict": true,
|
"esnext"
|
||||||
"forceConsistentCasingInFileNames": true,
|
],
|
||||||
"noImplicitAny": true,
|
"allowJs": true,
|
||||||
"strictNullChecks": true,
|
"skipLibCheck": true,
|
||||||
"strictFunctionTypes": true,
|
"strict": true,
|
||||||
"strictBindCallApply": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"strictPropertyInitialization": true,
|
"noImplicitAny": true,
|
||||||
"noImplicitThis": true,
|
"strictNullChecks": true,
|
||||||
"alwaysStrict": true,
|
"strictFunctionTypes": true,
|
||||||
"noUnusedLocals": false,
|
"strictBindCallApply": true,
|
||||||
"noUnusedParameters": true,
|
"strictPropertyInitialization": true,
|
||||||
"noEmit": true,
|
"noImplicitThis": true,
|
||||||
"esModuleInterop": true,
|
"alwaysStrict": true,
|
||||||
"module": "esnext",
|
"noUnusedLocals": false,
|
||||||
"moduleResolution": "node",
|
"noUnusedParameters": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"noEmit": true,
|
||||||
"resolveJsonModule": true,
|
"esModuleInterop": true,
|
||||||
"isolatedModules": true,
|
"module": "esnext",
|
||||||
"jsx": "preserve",
|
"moduleResolution": "node",
|
||||||
"incremental": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"baseUrl": ".",
|
"resolveJsonModule": true,
|
||||||
"paths": {
|
"isolatedModules": true,
|
||||||
"@components/*": ["src/app/components/*"],
|
"jsx": "preserve",
|
||||||
"@lib/*": ["src/lib/*"],
|
"incremental": true,
|
||||||
"@styles/*": ["src/app/styles/*"]
|
"baseUrl": ".",
|
||||||
}
|
"paths": {
|
||||||
},
|
"@components/*": [
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"src/app/components/*"
|
||||||
"exclude": ["node_modules"]
|
],
|
||||||
|
"@lib/*": [
|
||||||
|
"src/lib/*"
|
||||||
|
],
|
||||||
|
"@styles/*": [
|
||||||
|
"src/app/styles/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue