Skip to main content
File Operations

Resumable Upload (tus-style)

DownloadValidateExpert

Description

Upload a file in chunks, survive an interruption, and resume from the server-reported offset instead of starting over.

Test Scenario

// Playwright: upload 20 bytes in two chunks, then prove resume works
const create = await request.post('/api/v1/upload/resumable', {
  headers: { 'Upload-Length': '20' },
});

expect(create.status()).toBe(201);

const id = new URL(create.headers()['location'], 'http://x').searchParams.get('id');

// First chunk
const first = await request.patch(`/api/v1/upload/resumable?id=${id}`, {
  headers: { 'Upload-Offset': '0', 'Content-Type': 'application/offset+octet-stream' },
  data: 'ABCDEFGHIJ',
});

expect(first.headers()['upload-offset']).toBe('10');

// Replaying the same offset is a stale write โ€” the server refuses it
const stale = await request.patch(`/api/v1/upload/resumable?id=${id}`, {
  headers: { 'Upload-Offset': '0' },
  data: 'XXXX',
});

expect(stale.status()).toBe(409);

// Ask the server where to resume, then finish
const head = await request.head(`/api/v1/upload/resumable?id=${id}`);
const offset = head.headers()['upload-offset'];

const second = await request.patch(`/api/v1/upload/resumable?id=${id}`, {
  headers: { 'Upload-Offset': offset, 'Content-Type': 'application/offset+octet-stream' },
  data: 'KLMNOPQRST',
});

expect(second.headers()['upload-offset']).toBe('20');
Endpoint/api/v1/upload/resumable

Testing Tips

  • โ€ขPOST with Upload-Length opens a session โ€” read the id from the Location header
  • โ€ขHEAD returns the authoritative Upload-Offset; never trust your own byte count after a failure
  • โ€ขPATCH with a stale Upload-Offset is rejected with 409 โ€” re-read the offset and continue from there
  • โ€ขExceeding the declared Upload-Length returns 413; DELETE cancels the session
  • โ€ขSession state is per-instance and expires after 10 minutes โ€” a resumed test must tolerate a cold session
// Playwright API Testing - File Operations
import { test, expect } from '@playwright/test';
test('File download', async ({ request }) => {
const response = await request.get('/api/files/download/report.pdf');
expect(response.ok()).toBeTruthy();
expect(response.headers()['content-type']).toContain('application/pdf');
const buffer = await response.body();
expect(buffer.length).toBeGreaterThan(0);
});

Challenge ID: files-resumable-upload

2 frameworks available

API Playground

Send requests and inspect responses

Endpoint: /api/v1/upload/resumable

Press Enter to send