fix: don't send Content-Type on bodyless DELETE, include full server data in project queries #34

Merged
michal merged 1 commits from fix/delete-content-type-and-project-servers into main 2026-02-23 19:55:36 +00:00
3 changed files with 22 additions and 3 deletions
Showing only changes of commit e4affe5962 - Show all commits

View File

@@ -24,7 +24,10 @@ export class ApiError extends Error {
function request<T>(method: string, url: string, timeout: number, body?: unknown, token?: string): Promise<ApiResponse<T>> {
return new Promise((resolve, reject) => {
const parsed = new URL(url);
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
const headers: Record<string, string> = {};
if (body !== undefined) {
headers['Content-Type'] = 'application/json';
}
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}

View File

@@ -21,6 +21,16 @@ beforeAll(async () => {
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ id: 'srv-new', ...body }));
});
} else if (req.url === '/api/v1/servers/srv-1' && req.method === 'DELETE') {
// Fastify rejects empty body with Content-Type: application/json
const ct = req.headers['content-type'] ?? '';
if (ct.includes('application/json')) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: "Body cannot be empty when content-type is set to 'application/json'" }));
} else {
res.writeHead(204);
res.end();
}
} else if (req.url === '/api/v1/missing' && req.method === 'GET') {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Not found' }));
@@ -75,6 +85,12 @@ describe('ApiClient', () => {
await expect(client.get('/anything')).rejects.toThrow();
});
it('performs DELETE without Content-Type header', async () => {
const client = new ApiClient({ baseUrl: `http://localhost:${port}` });
// Should succeed (204) because no Content-Type is sent on bodyless DELETE
await expect(client.delete('/api/v1/servers/srv-1')).resolves.toBeUndefined();
});
it('sends Authorization header when token provided', async () => {
// We need a separate server to check the header
let receivedAuth = '';

View File

@@ -1,11 +1,11 @@
import type { PrismaClient, Project } from '@prisma/client';
export interface ProjectWithRelations extends Project {
servers: Array<{ id: string; server: { id: string; name: string } }>;
servers: Array<{ id: string; projectId: string; serverId: string; server: Record<string, unknown> & { id: string; name: string } }>;
}
const PROJECT_INCLUDE = {
servers: { include: { server: { select: { id: true, name: true } } } },
servers: { include: { server: true } },
} as const;
export interface IProjectRepository {