From b678f0181aaf124c87f4f93aae49a9419564e54f Mon Sep 17 00:00:00 2001 From: Egor Mikheev Date: Wed, 26 Nov 2025 00:01:33 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BE=D0=B3=D1=80=D0=B0=D0=BD=D0=B8=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=BE=D1=81=D1=82=D1=83=D0=BF=D0=B0?= =?UTF-8?q?=20=D0=BE=D1=80=D0=B3=D0=B0=D0=BD=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/tests/e2e/access-control.test.ts | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 apps/backend/src/tests/e2e/access-control.test.ts diff --git a/apps/backend/src/tests/e2e/access-control.test.ts b/apps/backend/src/tests/e2e/access-control.test.ts new file mode 100644 index 0000000..0fe2c01 --- /dev/null +++ b/apps/backend/src/tests/e2e/access-control.test.ts @@ -0,0 +1,145 @@ +// apps/backend/src/tests/e2e/access-control.test.ts +// Path: apps/backend/src/tests/e2e/access-control.test.ts + +import { describe, expect, test, beforeAll } from 'bun:test'; + +describe('E2E: Access Control', () => { + let user1Cookie: string; + let user1Id: string; + let user2Cookie: string; + let user2Id: string; + let linkSaveId: number; + let linkShareUrl: string; + let publicSaveId: number; + + beforeAll(async () => { + // Создаем первого пользователя + const user1SignUp = await fetch('http://localhost:3000/auth/api/sign-up/email', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: 'User 1', + email: `user1-${Date.now()}@example.com`, + password: 'Password123!', + }), + }); + + const user1Data = await user1SignUp.json(); + user1Id = user1Data.user.id; + user1Cookie = user1SignUp.headers.get('set-cookie') || ''; + + // Создаем второго пользователя + const user2SignUp = await fetch('http://localhost:3000/auth/api/sign-up/email', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: 'User 2', + email: `user2-${Date.now()}@example.com`, + password: 'Password123!', + }), + }); + + const user2Data = await user2SignUp.json(); + user2Id = user2Data.user.id; + user2Cookie = user2SignUp.headers.get('set-cookie') || ''; + + // User 1 создает сейв с visibility: link + const linkSaveResponse = await fetch('http://localhost:3000/saves/external', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Cookie': user1Cookie, + }, + body: JSON.stringify({ + url: 'https://httpbin.org/image/png', + name: 'Link Save', + visibility: 'link', + }), + }); + + const linkSaveData = await linkSaveResponse.json(); + linkSaveId = linkSaveData.id; + linkShareUrl = linkSaveData.shareUrl; + + // User 1 создает публичный сейв + const publicSaveResponse = await fetch('http://localhost:3000/saves/external', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Cookie': user1Cookie, + }, + body: JSON.stringify({ + url: 'https://httpbin.org/image/jpeg', + name: 'Public Save', + visibility: 'public', + }), + }); + + const publicSaveData = await publicSaveResponse.json(); + publicSaveId = publicSaveData.id; + }); + + test('owner should access link save', async () => { + const response = await fetch(`http://localhost:3000/saves/${linkSaveId}`, { + headers: { 'Cookie': user1Cookie }, + }); + + expect(response.status).toBe(200); + }); + + test('non-owner should NOT access link save without share token', async () => { + const response = await fetch(`http://localhost:3000/saves/${linkSaveId}`, { + headers: { 'Cookie': user2Cookie }, + }); + + expect(response.status).toBe(404); + }); + + test('non-owner should access link save WITH share token', async () => { + const response = await fetch( + `http://localhost:3000/saves/${linkSaveId}?share=${linkShareUrl}`, + { + headers: { 'Cookie': user2Cookie }, + } + ); + + expect(response.status).toBe(200); + + const data = await response.json(); + expect(data.id).toBe(linkSaveId); + }); + + test('anyone should access public save', async () => { + const response = await fetch(`http://localhost:3000/saves/${publicSaveId}`); + + expect(response.status).toBe(200); + + const data = await response.json(); + expect(data.id).toBe(publicSaveId); + expect(data.visibility).toBe('public'); + }); + + test('non-owner should NOT be able to update save', async () => { + const response = await fetch(`http://localhost:3000/saves/${publicSaveId}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'Cookie': user2Cookie, + }, + body: JSON.stringify({ + name: 'Hacked Name', + }), + }); + + expect(response.status).toBeGreaterThanOrEqual(400); + }); + + test('non-owner should NOT be able to delete save', async () => { + const response = await fetch(`http://localhost:3000/saves/${publicSaveId}`, { + method: 'DELETE', + headers: { 'Cookie': user2Cookie }, + }); + + expect(response.status).toBeGreaterThanOrEqual(400); + }); +});