feat: add API unit tests
Add Bun test suite covering: - Auth: register, login, duplicate email, invalid password - Events: create, location, validation, unauthorized - Journals: get, generate, delete, list with pagination - Settings: get, update, clear prompt, change password - Export/Import: export data, events with location - Search: search events, no matches, cross-date - Days: list days, get day details, empty day
This commit is contained in:
@@ -18,6 +18,7 @@ Self-hosted journaling app where users capture events throughout the day and AI
|
||||
/src
|
||||
index.ts # Main API routes, journal generation logic
|
||||
/services/ai # AI provider implementations (Groq, OpenAI, Anthropic, Ollama, LMStudio)
|
||||
/__tests__/ # API unit tests (Bun test)
|
||||
|
||||
/frontend
|
||||
/src
|
||||
@@ -191,6 +192,13 @@ bunx prisma migrate dev --name migration_name
|
||||
docker compose build && docker compose up -d
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
```bash
|
||||
cd backend
|
||||
bun run test:server
|
||||
```
|
||||
Tests require the server running. The test script starts the server, runs tests, then stops it.
|
||||
|
||||
## Version History
|
||||
- 0.1.0: Automatic geolocation capture, Starlight documentation site
|
||||
- 0.0.6: Automatic geolocation capture on event creation, reverse geocoding to place names
|
||||
|
||||
2
backend/bunfig.toml
Normal file
2
backend/bunfig.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[test]
|
||||
preload = ["./src/__tests__/helpers.ts"]
|
||||
@@ -9,7 +9,10 @@
|
||||
"db:generate": "prisma generate",
|
||||
"db:push": "prisma db push",
|
||||
"db:migrate": "prisma migrate dev",
|
||||
"db:studio": "prisma studio"
|
||||
"db:studio": "prisma studio",
|
||||
"test": "bun test",
|
||||
"test:watch": "bun test --watch",
|
||||
"test:server": "bun run src/index.ts & sleep 2 && bun test && kill $!"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.22.0",
|
||||
|
||||
57
backend/src/__tests__/auth.test.ts
Normal file
57
backend/src/__tests__/auth.test.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { describe, it, expect, beforeAll } from 'bun:test';
|
||||
import { createTestUser, createEvent } from './helpers';
|
||||
|
||||
describe('Auth API', () => {
|
||||
it('should register a new user', async () => {
|
||||
const res = await fetch('http://localhost:3000/api/v1/auth/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
email: `newuser_${Date.now()}@test.com`,
|
||||
password: 'password123',
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(res.status).toBe(201);
|
||||
expect(json.data).toBeDefined();
|
||||
expect(json.data.apiKey).toBeDefined();
|
||||
});
|
||||
|
||||
it('should reject duplicate email', async () => {
|
||||
const email = `duplicate_${Date.now()}@test.com`;
|
||||
await createTestUser(email, 'password123');
|
||||
|
||||
const res = await fetch('http://localhost:3000/api/v1/auth/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password: 'password123' }),
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it('should login with valid credentials', async () => {
|
||||
const email = `login_${Date.now()}@test.com`;
|
||||
await createTestUser(email, 'mypassword');
|
||||
|
||||
const res = await fetch('http://localhost:3000/api/v1/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password: 'mypassword' }),
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(res.status).toBe(200);
|
||||
expect(json.data.token).toBeDefined();
|
||||
});
|
||||
|
||||
it('should reject invalid password', async () => {
|
||||
const email = `invalid_${Date.now()}@test.com`;
|
||||
await createTestUser(email, 'correctpassword');
|
||||
|
||||
const res = await fetch('http://localhost:3000/api/v1/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password: 'wrongpassword' }),
|
||||
});
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
});
|
||||
45
backend/src/__tests__/days.test.ts
Normal file
45
backend/src/__tests__/days.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { describe, it, expect, beforeEach } from 'bun:test';
|
||||
import { getApiKey, createEvent } from './helpers';
|
||||
|
||||
describe('Days API', () => {
|
||||
let apiKey: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
apiKey = await getApiKey();
|
||||
});
|
||||
|
||||
it('should list days with events', async () => {
|
||||
await createEvent(apiKey, '2026-03-25', 'text', 'Day listing test');
|
||||
|
||||
const res = await fetch('http://localhost:3000/api/v1/days', {
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` },
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(json.data).toBeDefined();
|
||||
expect(Array.isArray(json.data.days)).toBe(true);
|
||||
});
|
||||
|
||||
it('should get day with events and journal', async () => {
|
||||
const date = '2026-03-26';
|
||||
await createEvent(apiKey, date, 'text', 'Day details test');
|
||||
|
||||
const res = await fetch(`http://localhost:3000/api/v1/days/${date}`, {
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` },
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(json.data).toBeDefined();
|
||||
expect(json.data.date).toBe(date);
|
||||
expect(json.data.events).toBeDefined();
|
||||
expect(Array.isArray(json.data.events)).toBe(true);
|
||||
expect(json.data.events.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should return empty events for day with no events', async () => {
|
||||
const res = await fetch('http://localhost:3000/api/v1/days/2099-12-31', {
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` },
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(json.data.events.length).toBe(0);
|
||||
expect(json.data.journal).toBeNull();
|
||||
});
|
||||
});
|
||||
98
backend/src/__tests__/events.test.ts
Normal file
98
backend/src/__tests__/events.test.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { describe, it, expect, beforeEach } from 'bun:test';
|
||||
import { getApiKey, createEvent } from './helpers';
|
||||
|
||||
describe('Events API', () => {
|
||||
let apiKey: string;
|
||||
const testDate = '2026-03-27';
|
||||
|
||||
beforeEach(async () => {
|
||||
apiKey = await getApiKey();
|
||||
});
|
||||
|
||||
it('should create an event', async () => {
|
||||
const res = await createEvent(apiKey, testDate, 'text', 'Test event content');
|
||||
expect(res.data).toBeDefined();
|
||||
expect(res.data.content).toBe('Test event content');
|
||||
expect(res.data.type).toBe('text');
|
||||
expect(res.data.date).toBe(testDate);
|
||||
});
|
||||
|
||||
it('should create event with location', async () => {
|
||||
const res = await fetch('http://localhost:3000/api/v1/events', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
date: testDate,
|
||||
type: 'text',
|
||||
content: 'Event with location',
|
||||
latitude: 40.7128,
|
||||
longitude: -74.006,
|
||||
placeName: 'New York, NY',
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(json.data.latitude).toBe(40.7128);
|
||||
expect(json.data.longitude).toBe(-74.006);
|
||||
expect(json.data.placeName).toBe('New York, NY');
|
||||
});
|
||||
|
||||
it('should reject event without content', async () => {
|
||||
const res = await fetch('http://localhost:3000/api/v1/events', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
date: testDate,
|
||||
type: 'text',
|
||||
content: '',
|
||||
}),
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it('should reject event with invalid type', async () => {
|
||||
const res = await fetch('http://localhost:3000/api/v1/events', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
date: testDate,
|
||||
type: 'invalid',
|
||||
content: 'Test',
|
||||
}),
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it('should get day events', async () => {
|
||||
await createEvent(apiKey, testDate, 'text', 'Event for day');
|
||||
|
||||
const res = await fetch(`http://localhost:3000/api/v1/days/${testDate}`, {
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` },
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(json.data).toBeDefined();
|
||||
expect(json.data.events).toBeDefined();
|
||||
expect(Array.isArray(json.data.events)).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject unauthorized request', async () => {
|
||||
const res = await fetch('http://localhost:3000/api/v1/events', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
date: testDate,
|
||||
type: 'text',
|
||||
content: 'Test',
|
||||
}),
|
||||
});
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
});
|
||||
61
backend/src/__tests__/export-import.test.ts
Normal file
61
backend/src/__tests__/export-import.test.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { describe, it, expect, beforeEach } from 'bun:test';
|
||||
import { getApiKey, createEvent } from './helpers';
|
||||
|
||||
describe('Export/Import API', () => {
|
||||
let apiKey: string;
|
||||
const testDate = '2026-03-29';
|
||||
|
||||
beforeEach(async () => {
|
||||
apiKey = await getApiKey();
|
||||
await createEvent(apiKey, testDate, 'text', 'Exportable event');
|
||||
});
|
||||
|
||||
it('should export user data', async () => {
|
||||
const res = await fetch('http://localhost:3000/api/v1/export', {
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` },
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(json.data).toBeDefined();
|
||||
expect(json.data.version).toBeDefined();
|
||||
expect(json.data.events).toBeDefined();
|
||||
expect(json.data.journals).toBeDefined();
|
||||
expect(json.data.settings).toBeDefined();
|
||||
expect(Array.isArray(json.data.events)).toBe(true);
|
||||
expect(Array.isArray(json.data.journals)).toBe(true);
|
||||
});
|
||||
|
||||
it('should export events with location', async () => {
|
||||
await fetch('http://localhost:3000/api/v1/events', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
date: testDate,
|
||||
type: 'text',
|
||||
content: 'Event with location for export',
|
||||
latitude: 51.5074,
|
||||
longitude: -0.1278,
|
||||
placeName: 'London, UK',
|
||||
}),
|
||||
});
|
||||
|
||||
const res = await fetch('http://localhost:3000/api/v1/export', {
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` },
|
||||
});
|
||||
const json = await res.json();
|
||||
|
||||
const exportedEvent = json.data.events.find(
|
||||
(e: any) => e.content === 'Event with location for export'
|
||||
);
|
||||
expect(exportedEvent).toBeDefined();
|
||||
expect(exportedEvent.latitude).toBe(51.5074);
|
||||
expect(exportedEvent.placeName).toBe('London, UK');
|
||||
});
|
||||
|
||||
it('should reject unauthorized export', async () => {
|
||||
const res = await fetch('http://localhost:3000/api/v1/export');
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
});
|
||||
95
backend/src/__tests__/helpers.ts
Normal file
95
backend/src/__tests__/helpers.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'bun:test';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
export const prisma = new PrismaClient({
|
||||
datasourceUrl: 'file:./data/test.db',
|
||||
});
|
||||
|
||||
export const TEST_USER = {
|
||||
email: 'test@example.com',
|
||||
password: 'testpassword123',
|
||||
};
|
||||
|
||||
export const SECOND_USER = {
|
||||
email: 'second@example.com',
|
||||
password: 'secondpassword123',
|
||||
};
|
||||
|
||||
let testApiKey: string;
|
||||
let secondApiKey: string;
|
||||
|
||||
export async function createTestUser(email: string, password: string): Promise<string> {
|
||||
const res = await fetch('http://localhost:3000/api/v1/auth/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
const json = await res.json();
|
||||
return json.data.apiKey;
|
||||
}
|
||||
|
||||
export async function loginUser(email: string, password: string): Promise<string> {
|
||||
const res = await fetch('http://localhost:3000/api/v1/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
const json = await res.json();
|
||||
|
||||
const loginRes = await fetch('http://localhost:3000/api/v1/auth/api-key', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${json.data.token}`,
|
||||
},
|
||||
});
|
||||
const keyJson = await loginRes.json();
|
||||
return keyJson.data.apiKey;
|
||||
}
|
||||
|
||||
export async function createEvent(apiKey: string, date: string, type: string, content: string) {
|
||||
const res = await fetch('http://localhost:3000/api/v1/events', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({ date, type, content }),
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function getApiKey() {
|
||||
if (!testApiKey) {
|
||||
try {
|
||||
testApiKey = await createTestUser(TEST_USER.email, TEST_USER.password);
|
||||
} catch {
|
||||
testApiKey = await loginUser(TEST_USER.email, TEST_USER.password);
|
||||
}
|
||||
}
|
||||
return testApiKey;
|
||||
}
|
||||
|
||||
export async function getSecondApiKey() {
|
||||
if (!secondApiKey) {
|
||||
try {
|
||||
secondApiKey = await createTestUser(SECOND_USER.email, SECOND_USER.password);
|
||||
} catch {
|
||||
secondApiKey = await loginUser(SECOND_USER.email, SECOND_USER.password);
|
||||
}
|
||||
}
|
||||
return secondApiKey;
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
await prisma.event.deleteMany({ where: { user: { email: TEST_USER.email } } });
|
||||
await prisma.journal.deleteMany({ where: { user: { email: TEST_USER.email } } });
|
||||
await prisma.task.deleteMany({ where: { user: { email: TEST_USER.email } } });
|
||||
await prisma.event.deleteMany({ where: { user: { email: SECOND_USER.email } } });
|
||||
await prisma.journal.deleteMany({ where: { user: { email: SECOND_USER.email } } });
|
||||
await prisma.task.deleteMany({ where: { user: { email: SECOND_USER.email } } });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
55
backend/src/__tests__/journals.test.ts
Normal file
55
backend/src/__tests__/journals.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { describe, it, expect, beforeEach } from 'bun:test';
|
||||
import { getApiKey, createEvent } from './helpers';
|
||||
|
||||
describe('Journals API', () => {
|
||||
let apiKey: string;
|
||||
const testDate = '2026-03-28';
|
||||
|
||||
beforeEach(async () => {
|
||||
apiKey = await getApiKey();
|
||||
});
|
||||
|
||||
it('should get empty day for journal', async () => {
|
||||
const res = await fetch(`http://localhost:3000/api/v1/journal/${testDate}`, {
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` },
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(res.status).toBe(404);
|
||||
expect(json.error.code).toBe('NOT_FOUND');
|
||||
});
|
||||
|
||||
it('should reject journal generation with no events', async () => {
|
||||
const res = await fetch(`http://localhost:3000/api/v1/journal/generate/${testDate}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
},
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(res.status).toBe(400);
|
||||
expect(json.error.code).toBe('NO_EVENTS');
|
||||
});
|
||||
|
||||
it('should delete journal', async () => {
|
||||
await createEvent(apiKey, testDate, 'text', 'Test event');
|
||||
|
||||
const deleteRes = await fetch(`http://localhost:3000/api/v1/journal/${testDate}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` },
|
||||
});
|
||||
const json = await deleteRes.json();
|
||||
expect(json.data.deleted).toBe(true);
|
||||
});
|
||||
|
||||
it('should list journals with pagination', async () => {
|
||||
const res = await fetch('http://localhost:3000/api/v1/journals?page=1&limit=10', {
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` },
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(json.data.journals).toBeDefined();
|
||||
expect(json.data.total).toBeDefined();
|
||||
expect(json.data.page).toBe(1);
|
||||
expect(json.data.limit).toBe(10);
|
||||
});
|
||||
});
|
||||
40
backend/src/__tests__/search.test.ts
Normal file
40
backend/src/__tests__/search.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { describe, it, expect, beforeEach } from 'bun:test';
|
||||
import { getApiKey, createEvent } from './helpers';
|
||||
|
||||
describe('Search API', () => {
|
||||
let apiKey: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
apiKey = await getApiKey();
|
||||
});
|
||||
|
||||
it('should search events', async () => {
|
||||
await createEvent(apiKey, '2026-03-30', 'text', 'Unique search term banana apple');
|
||||
|
||||
const res = await fetch('http://localhost:3000/api/v1/search?q=banana', {
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` },
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(json.data).toBeDefined();
|
||||
expect(json.data.events).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return empty results for no matches', async () => {
|
||||
const res = await fetch('http://localhost:3000/api/v1/search?q=nonexistentterm12345', {
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` },
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(json.data.events.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should search across all dates', async () => {
|
||||
await createEvent(apiKey, '2026-03-15', 'text', 'Searchable content from march');
|
||||
await createEvent(apiKey, '2026-03-20', 'text', 'Searchable content from march again');
|
||||
|
||||
const res = await fetch('http://localhost:3000/api/v1/search?q=march', {
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` },
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(json.data.events.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
});
|
||||
71
backend/src/__tests__/settings.test.ts
Normal file
71
backend/src/__tests__/settings.test.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { describe, it, expect, beforeEach } from 'bun:test';
|
||||
import { getApiKey } from './helpers';
|
||||
|
||||
describe('Settings API', () => {
|
||||
let apiKey: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
apiKey = await getApiKey();
|
||||
});
|
||||
|
||||
it('should get default settings', async () => {
|
||||
const res = await fetch('http://localhost:3000/api/v1/settings', {
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` },
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(json.data).toBeDefined();
|
||||
expect(json.data.aiProvider).toBeDefined();
|
||||
});
|
||||
|
||||
it('should update settings', async () => {
|
||||
const res = await fetch('http://localhost:3000/api/v1/settings', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
aiProvider: 'openai',
|
||||
journalPrompt: 'Write in a poetic style',
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(json.data.aiProvider).toBe('openai');
|
||||
expect(json.data.journalPrompt).toBe('Write in a poetic style');
|
||||
});
|
||||
|
||||
it('should clear journal prompt with empty string', async () => {
|
||||
await fetch('http://localhost:3000/api/v1/settings', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({ journalPrompt: '' }),
|
||||
});
|
||||
|
||||
const getRes = await fetch('http://localhost:3000/api/v1/settings', {
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` },
|
||||
});
|
||||
const json = await getRes.json();
|
||||
expect(json.data.journalPrompt).toBeNull();
|
||||
});
|
||||
|
||||
it('should change password', async () => {
|
||||
const newPassword = 'newpassword456';
|
||||
|
||||
const res = await fetch('http://localhost:3000/api/v1/account/password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
currentPassword: 'testpassword123',
|
||||
newPassword,
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
expect(json.data).toBeDefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user