added tests
This commit is contained in:
parent
21308fa5bd
commit
027847f996
12 changed files with 211 additions and 18 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
|
@ -16,6 +16,13 @@ CREATE TABLE "auth"."account" (
|
||||||
"updated_at" timestamp NOT NULL
|
"updated_at" timestamp NOT NULL
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "auth"."jwks" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"public_key" text NOT NULL,
|
||||||
|
"private_key" text NOT NULL,
|
||||||
|
"created_at" timestamp NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
CREATE TABLE "auth"."rate_limit" (
|
CREATE TABLE "auth"."rate_limit" (
|
||||||
"id" text PRIMARY KEY NOT NULL,
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
"key" text,
|
"key" text,
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"id": "50bd2c27-8d45-478f-a894-b2f7cd4a718b",
|
"id": "38b37b1d-93e4-4f9a-8d7e-a77b0632afb4",
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"dialect": "postgresql",
|
"dialect": "postgresql",
|
||||||
|
|
@ -110,6 +110,43 @@
|
||||||
"checkConstraints": {},
|
"checkConstraints": {},
|
||||||
"isRLSEnabled": false
|
"isRLSEnabled": false
|
||||||
},
|
},
|
||||||
|
"auth.jwks": {
|
||||||
|
"name": "jwks",
|
||||||
|
"schema": "auth",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"public_key": {
|
||||||
|
"name": "public_key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"private_key": {
|
||||||
|
"name": "private_key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
"auth.rate_limit": {
|
"auth.rate_limit": {
|
||||||
"name": "rate_limit",
|
"name": "rate_limit",
|
||||||
"schema": "auth",
|
"schema": "auth",
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1737019435130,
|
"when": 1737386379330,
|
||||||
"tag": "0000_uneven_professor_monster",
|
"tag": "0000_ambitious_jocasta",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
17
package.json
17
package.json
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"version": "1.0.50",
|
"version": "1.0.50",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "bun test api",
|
||||||
"dev": "bun run --watch src/index.ts",
|
"dev": "bun run --watch src/index.ts",
|
||||||
"email": "email dev --dir src/emails",
|
"email": "email dev --dir src/emails",
|
||||||
"auth:generate": "bun x @better-auth/cli generate --config src/lib/auth/auth.ts --output src/db/schema/auth.ts && drizzle-kit migrate",
|
"auth:generate": "bun x @better-auth/cli generate --config src/lib/auth/auth.ts --output src/db/schema/auth.ts && drizzle-kit migrate",
|
||||||
|
|
@ -16,14 +16,15 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@elysiajs/cors": "^1.2.0",
|
"@elysiajs/cors": "^1.2.0",
|
||||||
|
"@elysiajs/eden": "^1.2.0",
|
||||||
"@elysiajs/opentelemetry": "^1.2.0",
|
"@elysiajs/opentelemetry": "^1.2.0",
|
||||||
"@elysiajs/server-timing": "^1.2.0",
|
"@elysiajs/server-timing": "^1.2.0",
|
||||||
"@elysiajs/swagger": "^1.2.0",
|
"@elysiajs/swagger": "^1.2.0",
|
||||||
"@paralleldrive/cuid2": "^2.2.2",
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
"@react-email/components": "^0.0.31",
|
"@react-email/components": "^0.0.32",
|
||||||
"better-auth": "^1.1.10",
|
"better-auth": "^1.1.14",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"drizzle-orm": "^0.38.3",
|
"drizzle-orm": "^0.38.4",
|
||||||
"drizzle-typebox": "^0.2.1",
|
"drizzle-typebox": "^0.2.1",
|
||||||
"elysia": "latest",
|
"elysia": "latest",
|
||||||
"minio": "^8.0.3",
|
"minio": "^8.0.3",
|
||||||
|
|
@ -38,11 +39,11 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/nodemailer": "^6.4.17",
|
"@types/nodemailer": "^6.4.17",
|
||||||
"@types/pg": "^8.11.10",
|
"@types/pg": "^8.11.10",
|
||||||
"@types/react": "^19.0.3",
|
"@types/react": "^19.0.7",
|
||||||
"@types/react-dom": "^19.0.2",
|
"@types/react-dom": "^19.0.3",
|
||||||
"bun-types": "latest",
|
"bun-types": "latest",
|
||||||
"drizzle-kit": "^0.30.1",
|
"drizzle-kit": "^0.30.2",
|
||||||
"react-email": "^3.0.4",
|
"react-email": "^3.0.6",
|
||||||
"tsx": "^4.19.2"
|
"tsx": "^4.19.2"
|
||||||
},
|
},
|
||||||
"module": "src/index.js"
|
"module": "src/index.js"
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,12 @@ export class NoteController {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.execute();
|
.execute();
|
||||||
|
let successStatus = true;
|
||||||
|
if(result.length===0){
|
||||||
|
successStatus = false
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: successStatus,
|
||||||
data: result,
|
data: result,
|
||||||
message: "",
|
message: "",
|
||||||
error: null,
|
error: null,
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export const noteRouter = new Elysia({
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.get(
|
.get(
|
||||||
":id",
|
"/:id",
|
||||||
async ({ note, user, params:{id} }) => {
|
async ({ note, user, params:{id} }) => {
|
||||||
return await note.getNoteById(id, user.id);
|
return await note.getNoteById(id, user.id);
|
||||||
},
|
},
|
||||||
|
|
@ -73,7 +73,7 @@ export const noteRouter = new Elysia({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).patch(
|
).patch(
|
||||||
":id",
|
"/:id",
|
||||||
async ({ body, note, user, params:{id} }) => {
|
async ({ body, note, user, params:{id} }) => {
|
||||||
return await note.updateNoteById(id, body, user.id);
|
return await note.updateNoteById(id, body, user.id);
|
||||||
},
|
},
|
||||||
|
|
@ -89,7 +89,7 @@ export const noteRouter = new Elysia({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).delete(
|
).delete(
|
||||||
":id",
|
"/:id",
|
||||||
async ({ note, user, params:{id} }) => {
|
async ({ note, user, params:{id} }) => {
|
||||||
return await note.deleteNoteById(id, user.id);
|
return await note.deleteNoteById(id, user.id);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
102
src/api/routes/note/note.test.ts
Normal file
102
src/api/routes/note/note.test.ts
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
import { describe, expect, it } from 'bun:test'
|
||||||
|
import { testClientApp } from '../../../../test/client';
|
||||||
|
|
||||||
|
let noteId: string;
|
||||||
|
|
||||||
|
describe('Note', () => {
|
||||||
|
// Create a note before tests
|
||||||
|
it('Create Note', async () => {
|
||||||
|
const { data } = await testClientApp.api.note.post({
|
||||||
|
"title": "test note",
|
||||||
|
"content": "description",
|
||||||
|
})
|
||||||
|
if (!data?.data) {
|
||||||
|
throw new Error('create note api did not return data');
|
||||||
|
}
|
||||||
|
noteId = data.data[0].id
|
||||||
|
expect(data.data[0].title).toBe('test note')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get all notes
|
||||||
|
it('Get All Notes', async () => {
|
||||||
|
const { data } = await testClientApp.api.note.get({
|
||||||
|
query: { limit: 1, offset: 0 }
|
||||||
|
})
|
||||||
|
expect(data?.data[0].id).toBe(noteId)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get single note
|
||||||
|
it('Get Created Note', async () => {
|
||||||
|
const { data, error } = await testClientApp.api.note({ id: noteId }).get()
|
||||||
|
expect(data?.data[0].id).toBe(noteId)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update note
|
||||||
|
it('Update Note', async () => {
|
||||||
|
const updatedTitle = "updated test note"
|
||||||
|
const updatedContent = "updated description"
|
||||||
|
|
||||||
|
const { data } = await testClientApp.api.note({ id: noteId }).patch({
|
||||||
|
title: updatedTitle,
|
||||||
|
content: updatedContent,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(data?.success).toBe(true)
|
||||||
|
expect(data?.data[0].title).toBe(updatedTitle)
|
||||||
|
expect(data?.data[0].content).toBe(updatedContent)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete single note
|
||||||
|
it('Delete Single Note', async () => {
|
||||||
|
// First create a new note to delete
|
||||||
|
const { data: createData } = await testClientApp.api.note.post({
|
||||||
|
"title": "note to delete",
|
||||||
|
"content": "this note will be deleted",
|
||||||
|
})
|
||||||
|
const deleteNoteId = createData?.data[0].id
|
||||||
|
|
||||||
|
|
||||||
|
if (!deleteNoteId) {
|
||||||
|
throw new Error('Failed to receive noteId in delete note test');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the note
|
||||||
|
const { data: deleteData } = await testClientApp.api.note({ id: deleteNoteId }).delete()
|
||||||
|
expect(deleteData?.success).toBe(true)
|
||||||
|
|
||||||
|
// Verify note is deleted by trying to fetch it
|
||||||
|
const { data: verifyData } = await testClientApp.api.note({ id: deleteNoteId }).get()
|
||||||
|
expect(verifyData?.data).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete all notes
|
||||||
|
it('Delete All Notes', async () => {
|
||||||
|
// First create multiple notes
|
||||||
|
await testClientApp.api.note.post({
|
||||||
|
"title": "note 1",
|
||||||
|
"content": "content 1",
|
||||||
|
})
|
||||||
|
await testClientApp.api.note.post({
|
||||||
|
"title": "note 2",
|
||||||
|
"content": "content 2",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete all notes
|
||||||
|
const { data: deleteData } = await testClientApp.api.note.delete()
|
||||||
|
expect(deleteData?.success).toBe(true)
|
||||||
|
|
||||||
|
// Verify all notes are deleted
|
||||||
|
const { data: verifyData } = await testClientApp.api.note.get({
|
||||||
|
query: { limit: 10, offset: 0 }
|
||||||
|
})
|
||||||
|
expect(verifyData?.data).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Error cases
|
||||||
|
it('Should handle invalid note ID', async () => {
|
||||||
|
const invalidId = 'invalid-id'
|
||||||
|
const { data } = await testClientApp.api.note({ id: invalidId }).get()
|
||||||
|
expect(data?.success).toBe(false)
|
||||||
|
expect(data?.data).toHaveLength(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -54,3 +54,10 @@ export const rateLimit = authSchema.table("rate_limit", {
|
||||||
count: integer('count'),
|
count: integer('count'),
|
||||||
lastRequest: integer('last_request')
|
lastRequest: integer('last_request')
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const jwks = authSchema.table("jwks", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
publicKey: text("public_key").notNull(),
|
||||||
|
privateKey: text("private_key").notNull(),
|
||||||
|
createdAt: timestamp("created_at").notNull(),
|
||||||
|
});
|
||||||
|
|
@ -9,7 +9,8 @@ import { api } from "./api";
|
||||||
const baseConfig = getBaseConfig();
|
const baseConfig = getBaseConfig();
|
||||||
|
|
||||||
validateEnv();
|
validateEnv();
|
||||||
const app = new Elysia()
|
|
||||||
|
export const app = new Elysia()
|
||||||
.use(cors())
|
.use(cors())
|
||||||
.use(
|
.use(
|
||||||
opentelemetry({
|
opentelemetry({
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from "better-auth";
|
||||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||||
import { db } from "../../db/index";
|
import { db } from "../../db/index";
|
||||||
import { openAPI } from "better-auth/plugins"
|
import { jwt, openAPI } from "better-auth/plugins"
|
||||||
import { user, account, verification, session, rateLimit } from "../../db/schema/auth";
|
import { user, account, verification, session, rateLimit, jwks } from "../../db/schema/auth";
|
||||||
import { sendMail } from "../mail/mail";
|
import { sendMail } from "../mail/mail";
|
||||||
import { renderToStaticMarkup } from "react-dom/server";
|
import { renderToStaticMarkup } from "react-dom/server";
|
||||||
import { createElement } from "react";
|
import { createElement } from "react";
|
||||||
|
|
@ -17,8 +17,14 @@ export const auth = betterAuth({
|
||||||
account: account,
|
account: account,
|
||||||
verification: verification,
|
verification: verification,
|
||||||
rateLimit: rateLimit,
|
rateLimit: rateLimit,
|
||||||
|
jwks: jwks
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
user: {
|
||||||
|
deleteUser: {
|
||||||
|
enabled: true // [!Code Highlight]
|
||||||
|
}
|
||||||
|
},
|
||||||
rateLimit: {
|
rateLimit: {
|
||||||
window: 60,
|
window: 60,
|
||||||
max: 100,
|
max: 100,
|
||||||
|
|
@ -67,6 +73,7 @@ export const auth = betterAuth({
|
||||||
openAPI({
|
openAPI({
|
||||||
path: "/docs",
|
path: "/docs",
|
||||||
}),
|
}),
|
||||||
|
jwt()
|
||||||
],
|
],
|
||||||
socialProviders: {
|
socialProviders: {
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
27
test/client.ts
Normal file
27
test/client.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { treaty } from '@elysiajs/eden'
|
||||||
|
import { app } from '../src'
|
||||||
|
import { getAuthConfig } from '../src/lib/utils/env';
|
||||||
|
|
||||||
|
async function getAuthToken() {
|
||||||
|
const authUrl = getAuthConfig().BETTER_AUTH_URL
|
||||||
|
const response = await fetch(`${authUrl}/api/auth/sign-in/email`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: "test@test.com",
|
||||||
|
password: "testpass123"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const cookies = response.headers.getSetCookie()[0];
|
||||||
|
const sessionToken = cookies.split(";")[0].split("=")[1]
|
||||||
|
return sessionToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = await getAuthToken();
|
||||||
|
export const testClientApp = treaty(app,{
|
||||||
|
headers: {
|
||||||
|
Cookie: `better-auth.session_token=${token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
Loading…
Add table
Reference in a new issue