feat: get many endpoints (#2226)

Co-authored-by: Lucas Smith <me@lucasjamessmith.me>
This commit is contained in:
Catalin Pit
2025-12-24 02:02:02 +02:00
committed by GitHub
parent aa1cada79b
commit 90fdba8000
12 changed files with 927 additions and 0 deletions
@@ -41,6 +41,8 @@ import type {
TUseEnvelopeResponse,
} from '@documenso/trpc/server/envelope-router/use-envelope.types';
import { apiSignin } from '../../fixtures/authentication';
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
test.describe.configure({
@@ -2995,6 +2997,294 @@ test.describe('Document API V2', () => {
});
});
test.describe('Envelope get-many endpoint', () => {
test('should block unauthorized access to envelope get-many endpoint', async ({
request,
}) => {
const doc1 = await seedBlankDocument(userA, teamA.id);
const doc2 = await seedBlankDocument(userA, teamA.id);
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/get-many`, {
headers: { Authorization: `Bearer ${tokenB}` },
data: {
ids: {
type: 'envelopeId',
ids: [doc1.id, doc2.id],
},
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const { data } = await res.json();
expect(data).toEqual([]);
});
test('should allow authorized access to envelope get-many endpoint', async ({ request }) => {
const doc1 = await seedBlankDocument(userA, teamA.id);
const doc2 = await seedBlankDocument(userA, teamA.id);
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/get-many`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
ids: {
type: 'envelopeId',
ids: [doc1.id, doc2.id],
},
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const { data } = await res.json();
expect(data.length).toBe(2);
expect(data.map((d: { id: string }) => d.id).sort()).toEqual([doc1.id, doc2.id].sort());
});
test('should only return authorized envelopes when mixing owned and unowned', async ({
request,
}) => {
const docA = await seedBlankDocument(userA, teamA.id);
const docB = await seedBlankDocument(userB, teamB.id);
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/get-many`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
ids: {
type: 'envelopeId',
ids: [docA.id, docB.id],
},
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const { data } = await res.json();
expect(data.length).toBe(1);
expect(data[0].id).toBe(docA.id);
});
test('should block unauthorized access with documentId type', async ({ request }) => {
const doc1 = await seedBlankDocument(userA, teamA.id);
const doc2 = await seedBlankDocument(userA, teamA.id);
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/get-many`, {
headers: { Authorization: `Bearer ${tokenB}` },
data: {
ids: {
type: 'documentId',
ids: [
mapSecondaryIdToDocumentId(doc1.secondaryId),
mapSecondaryIdToDocumentId(doc2.secondaryId),
],
},
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const { data } = await res.json();
expect(data).toEqual([]);
});
test('should allow authorized access with documentId type', async ({ request }) => {
const doc1 = await seedBlankDocument(userA, teamA.id);
const doc2 = await seedBlankDocument(userA, teamA.id);
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/get-many`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
ids: {
type: 'documentId',
ids: [
mapSecondaryIdToDocumentId(doc1.secondaryId),
mapSecondaryIdToDocumentId(doc2.secondaryId),
],
},
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const { data } = await res.json();
expect(data.length).toBe(2);
});
test('should block unauthorized access with templateId type', async ({ request }) => {
const template1 = await seedBlankTemplate(userA, teamA.id);
const template2 = await seedBlankTemplate(userA, teamA.id);
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/get-many`, {
headers: { Authorization: `Bearer ${tokenB}` },
data: {
ids: {
type: 'templateId',
ids: [
mapSecondaryIdToTemplateId(template1.secondaryId),
mapSecondaryIdToTemplateId(template2.secondaryId),
],
},
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const { data } = await res.json();
expect(data).toEqual([]);
});
test('should allow authorized access with templateId type', async ({ request }) => {
const template1 = await seedBlankTemplate(userA, teamA.id);
const template2 = await seedBlankTemplate(userA, teamA.id);
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/get-many`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
ids: {
type: 'templateId',
ids: [
mapSecondaryIdToTemplateId(template1.secondaryId),
mapSecondaryIdToTemplateId(template2.secondaryId),
],
},
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const { data } = await res.json();
expect(data.length).toBe(2);
});
test('should reject requests exceeding max ID limit', async ({ request }) => {
const ids = Array.from({ length: 21 }, () => 'envelope_fake123');
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/get-many`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
ids: {
type: 'envelopeId',
ids,
},
},
});
expect(res.ok()).toBeFalsy();
expect(res.status()).toBe(400);
});
});
test.describe('Envelope get-many tRPC endpoint (teamId manipulation)', () => {
test('should block access when user manipulates x-team-id to another team', async ({
page,
}) => {
// Create documents for userA in teamA
const doc1 = await seedBlankDocument(userA, teamA.id);
const doc2 = await seedBlankDocument(userA, teamA.id);
// Sign in as userB
await apiSignin({ page, email: userB.email });
const res = await page
.context()
.request.post(`${WEBAPP_BASE_URL}/api/trpc/envelope.getMany`, {
headers: {
'x-team-id': String(teamA.id),
},
data: {
json: {
ids: {
type: 'envelopeId',
ids: [doc1.id, doc2.id],
},
},
},
});
// Make tRPC request with manipulated x-team-id pointing to teamA (which userB doesn't belong to)
expect(res.ok()).toBeFalsy();
// Team not found
expect(res.status()).toBe(404);
});
test('should allow access when user uses their own team id', async ({ page }) => {
// Create documents for userA in teamA
const doc1 = await seedBlankDocument(userA, teamA.id);
const doc2 = await seedBlankDocument(userA, teamA.id);
// Sign in as userA
await apiSignin({ page, email: userA.email });
const res = await page
.context()
.request.post(`${WEBAPP_BASE_URL}/api/trpc/envelope.getMany`, {
headers: {
'Content-Type': 'application/json',
'x-team-id': String(teamA.id),
},
data: {
json: {
ids: {
type: 'envelopeId',
ids: [doc1.id, doc2.id],
},
},
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const data = await res.json();
const items = data.result.data.json.data;
expect(items.length).toBe(2);
expect(items.map((d: { id: string }) => d.id).sort()).toEqual([doc1.id, doc2.id].sort());
});
test('should block access when switching team id mid-request to access other team data', async ({
page,
}) => {
// Create a document for userA in teamA
const docA = await seedBlankDocument(userA, teamA.id);
// Create a document for userB in teamB
const docB = await seedBlankDocument(userB, teamB.id);
// Sign in as userB
await apiSignin({ page, email: userB.email });
const res = await page
.context()
.request.post(`${WEBAPP_BASE_URL}/api/trpc/envelope.getMany`, {
headers: {
'x-team-id': String(teamA.id),
},
data: {
json: {
ids: {
type: 'envelopeId',
ids: [docA.id, docB.id],
},
},
},
});
// UserB tries to access both documents by manipulating teamId to teamA
// Should fail - userB is not a member of teamA
expect(res.ok()).toBeFalsy();
// Team not found
expect(res.status()).toBe(404);
});
});
test.describe('Envelope find endpoint', () => {
test('should block unauthorized access to envelope find endpoint', async ({ request }) => {
await seedBlankDocument(userA, teamA.id);