import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'bun:test' import type { FastifyInstance } from 'fastify' import { createTestApp, cleanDb, seedTestCompany, registerAndLogin } from '../../../src/test/helpers.js' describe('WebDAV', () => { let app: FastifyInstance let basicAuth: string beforeAll(async () => { app = await createTestApp() }) afterAll(async () => { await app.close() }) beforeEach(async () => { await cleanDb(app) await seedTestCompany(app) const { user } = await registerAndLogin(app, { email: 'webdav@forte.dev', password: 'webdavpass1234', }) basicAuth = 'Basic ' + Buffer.from('webdav@forte.dev:webdavpass1234').toString('base64') }) describe('OPTIONS', () => { it('returns DAV headers on root', async () => { const res = await app.inject({ method: 'OPTIONS', url: '/webdav/' }) expect(res.statusCode).toBe(200) expect(res.headers['dav']).toContain('1') expect(res.headers['allow']).toContain('PROPFIND') expect(res.headers['allow']).toContain('GET') expect(res.headers['allow']).toContain('PUT') }) it('returns DAV headers on wildcard path', async () => { const res = await app.inject({ method: 'OPTIONS', url: '/webdav/some/path' }) expect(res.statusCode).toBe(200) expect(res.headers['dav']).toContain('1') }) }) describe('Authentication', () => { it('returns 401 without credentials', async () => { const res = await app.inject({ method: 'PROPFIND' as any, url: '/webdav/', }) expect(res.statusCode).toBe(401) expect(res.headers['www-authenticate']).toContain('Basic') }) it('returns 401 with wrong password', async () => { const res = await app.inject({ method: 'PROPFIND' as any, url: '/webdav/', headers: { authorization: 'Basic ' + Buffer.from('webdav@forte.dev:wrongpass').toString('base64') }, }) expect(res.statusCode).toBe(401) }) it('succeeds with correct credentials', async () => { const res = await app.inject({ method: 'PROPFIND' as any, url: '/webdav/', headers: { authorization: basicAuth, depth: '0' }, }) expect(res.statusCode).toBe(207) }) }) describe('PROPFIND', () => { it('lists root with depth 0', async () => { const res = await app.inject({ method: 'PROPFIND' as any, url: '/webdav/', headers: { authorization: basicAuth, depth: '0' }, }) expect(res.statusCode).toBe(207) expect(res.headers['content-type']).toContain('application/xml') expect(res.body).toContain(' { // Create a folder first via MKCOL await app.inject({ method: 'MKCOL' as any, url: '/webdav/Test%20Folder', headers: { authorization: basicAuth }, }) const res = await app.inject({ method: 'PROPFIND' as any, url: '/webdav/', headers: { authorization: basicAuth, depth: '1' }, }) expect(res.statusCode).toBe(207) expect(res.body).toContain('Test Folder') }) }) describe('MKCOL', () => { it('creates a folder', async () => { const res = await app.inject({ method: 'MKCOL' as any, url: '/webdav/Documents', headers: { authorization: basicAuth }, }) expect(res.statusCode).toBe(201) // Verify it appears in PROPFIND const listing = await app.inject({ method: 'PROPFIND' as any, url: '/webdav/', headers: { authorization: basicAuth, depth: '1' }, }) expect(listing.body).toContain('Documents') }) it('returns 405 if folder already exists', async () => { await app.inject({ method: 'MKCOL' as any, url: '/webdav/Documents', headers: { authorization: basicAuth }, }) const res = await app.inject({ method: 'MKCOL' as any, url: '/webdav/Documents', headers: { authorization: basicAuth }, }) expect(res.statusCode).toBe(405) }) }) describe('PUT / GET / DELETE', () => { it('uploads, downloads, and deletes a file', async () => { // Create parent folder await app.inject({ method: 'MKCOL' as any, url: '/webdav/Uploads', headers: { authorization: basicAuth }, }) // PUT a file const fileContent = 'Hello, WebDAV!' const putRes = await app.inject({ method: 'PUT', url: '/webdav/Uploads/test.txt', headers: { authorization: basicAuth, 'content-type': 'text/plain', }, body: fileContent, }) expect(putRes.statusCode).toBe(201) expect(putRes.headers['etag']).toBeDefined() // GET the file const getRes = await app.inject({ method: 'GET', url: '/webdav/Uploads/test.txt', headers: { authorization: basicAuth }, }) expect(getRes.statusCode).toBe(200) expect(getRes.body).toBe(fileContent) expect(getRes.headers['content-type']).toContain('text/plain') // DELETE the file const delRes = await app.inject({ method: 'DELETE', url: '/webdav/Uploads/test.txt', headers: { authorization: basicAuth }, }) expect(delRes.statusCode).toBe(204) // Verify it's gone const getRes2 = await app.inject({ method: 'GET', url: '/webdav/Uploads/test.txt', headers: { authorization: basicAuth }, }) expect(getRes2.statusCode).toBe(404) }) it('overwrites an existing file with PUT', async () => { await app.inject({ method: 'MKCOL' as any, url: '/webdav/Overwrite', headers: { authorization: basicAuth }, }) // Upload original await app.inject({ method: 'PUT', url: '/webdav/Overwrite/doc.txt', headers: { authorization: basicAuth, 'content-type': 'text/plain' }, body: 'version 1', }) // Overwrite const putRes = await app.inject({ method: 'PUT', url: '/webdav/Overwrite/doc.txt', headers: { authorization: basicAuth, 'content-type': 'text/plain' }, body: 'version 2', }) expect(putRes.statusCode).toBe(204) // Verify new content const getRes = await app.inject({ method: 'GET', url: '/webdav/Overwrite/doc.txt', headers: { authorization: basicAuth }, }) expect(getRes.body).toBe('version 2') }) }) describe('DELETE folder', () => { it('deletes a folder', async () => { await app.inject({ method: 'MKCOL' as any, url: '/webdav/ToDelete', headers: { authorization: basicAuth }, }) const res = await app.inject({ method: 'DELETE', url: '/webdav/ToDelete', headers: { authorization: basicAuth }, }) expect(res.statusCode).toBe(204) }) }) describe('LOCK / UNLOCK', () => { it('returns a lock token', async () => { const res = await app.inject({ method: 'LOCK' as any, url: '/webdav/some-resource', headers: { authorization: basicAuth }, }) expect(res.statusCode).toBe(200) expect(res.headers['lock-token']).toContain('opaquelocktoken:') expect(res.body).toContain(' { const lockRes = await app.inject({ method: 'LOCK' as any, url: '/webdav/some-resource', headers: { authorization: basicAuth }, }) const lockToken = lockRes.headers['lock-token'] as string const unlockRes = await app.inject({ method: 'UNLOCK' as any, url: '/webdav/some-resource', headers: { authorization: basicAuth, 'lock-token': lockToken, }, }) expect(unlockRes.statusCode).toBe(204) }) }) describe('HEAD', () => { it('returns headers for a file', async () => { await app.inject({ method: 'MKCOL' as any, url: '/webdav/HeadTest', headers: { authorization: basicAuth }, }) await app.inject({ method: 'PUT', url: '/webdav/HeadTest/file.txt', headers: { authorization: basicAuth, 'content-type': 'text/plain' }, body: 'test content', }) const res = await app.inject({ method: 'HEAD', url: '/webdav/HeadTest/file.txt', headers: { authorization: basicAuth }, }) expect(res.statusCode).toBe(200) expect(res.headers['content-type']).toContain('text/plain') expect(res.headers['etag']).toBeDefined() }) }) })