import { test } from '@japa/runner' import { DateTime } from 'luxon' import User from '#models/user' import Component from '#models/component' import ComponentType from '#models/component_type' async function createUser(email: string) { return User.create({ name: 'Test User', email, password: 'secret@123A', }) } async function createType(name: string, unique = true) { return ComponentType.firstOrCreate({ name }, { name, unique }) } async function createComponent(userId: number, typeId: number) { return Component.create({ name: 'Test Component', price: 20, typeId, userId, computerId: null, purchaseDate: DateTime.now(), }) } test.group('Components authorization (IDOR)', () => { test('owner can update their own component', async ({ client, assert }) => { const owner = await createUser('owner-comp-update@example.com') const type = await createType('RAM-comp-1') const component = await createComponent(owner.id, type.id) const response = await client .put(`/components/${component.id}`) .loginAs(owner) .withCsrfToken() .redirects(0) .json({ name: 'Updated name', price: 30, typeId: type.id, purchaseDate: DateTime.now().toFormat('yyyy-MM-dd'), }) response.assertStatus(302) await component.refresh() assert.equal(component.name, 'Updated name') }) test('a user cannot update another user component', async ({ client, assert }) => { const owner = await createUser('owner-comp-a@example.com') const attacker = await createUser('attacker-comp-a@example.com') const type = await createType('RAM-comp-2') const component = await createComponent(owner.id, type.id) const response = await client .put(`/components/${component.id}`) .loginAs(attacker) .withCsrfToken() .redirects(0) .json({ name: 'Hacked name', price: 1, typeId: type.id, purchaseDate: DateTime.now().toFormat('yyyy-MM-dd'), }) response.assertStatus(404) await component.refresh() assert.notEqual(component.name, 'Hacked name') }) test('a user cannot delete another user component', async ({ client, assert }) => { const owner = await createUser('owner-comp-b@example.com') const attacker = await createUser('attacker-comp-b@example.com') const type = await createType('RAM-comp-3') const component = await createComponent(owner.id, type.id) const response = await client .delete(`/components/${component.id}`) .loginAs(attacker) .withCsrfToken() .redirects(0) response.assertStatus(404) const stillExists = await Component.find(component.id) assert.isNotNull(stillExists) }) test('an unauthenticated request cannot delete a component', async ({ client, assert }) => { const owner = await createUser('owner-comp-c@example.com') const type = await createType('RAM-comp-4') const component = await createComponent(owner.id, type.id) const response = await client.delete(`/components/${component.id}`) response.assertRedirectsTo('/auth/login') const stillExists = await Component.find(component.id) assert.isNotNull(stillExists) }) }) test.group('Components purchase URL', () => { test('a component can be created with a purchase site URL', async ({ client, assert }) => { const user = await createUser('purchase-url-a@example.com') const type = await createType('RAM-purchase-1') const response = await client .post('/components') .loginAs(user) .withCsrfToken() .redirects(0) .json({ name: 'New GPU', price: 599.99, typeId: type.id, purchaseDate: DateTime.now().toFormat('yyyy-MM-dd'), purchaseUrl: 'https://fr.pc-builder.io/gpu/example-p1', quantity: 1, }) response.assertStatus(302) const component = await Component.query().where('userId', user.id).firstOrFail() assert.equal(component.purchaseUrl, 'https://fr.pc-builder.io/gpu/example-p1') }) test('a component can be created without a purchase site URL', async ({ client, assert }) => { const user = await createUser('purchase-url-b@example.com') const type = await createType('RAM-purchase-2') const response = await client .post('/components') .loginAs(user) .withCsrfToken() .redirects(0) .json({ name: 'Used RAM stick', price: 20, typeId: type.id, purchaseDate: DateTime.now().toFormat('yyyy-MM-dd'), quantity: 1, }) response.assertStatus(302) const component = await Component.query().where('userId', user.id).firstOrFail() assert.isNull(component.purchaseUrl) }) test('an invalid purchase URL is rejected', async ({ client, assert }) => { const user = await createUser('purchase-url-c@example.com') const type = await createType('RAM-purchase-3') const response = await client .post('/components') .loginAs(user) .withCsrfToken() .redirects(0) .json({ name: 'Suspicious component', price: 20, typeId: type.id, purchaseDate: DateTime.now().toFormat('yyyy-MM-dd'), purchaseUrl: 'not-a-url', quantity: 1, }) response.assertStatus(302) assert.property(response.flashMessage('inputErrorsBag'), 'purchaseUrl') const created = await Component.query().where('userId', user.id).first() assert.isNull(created) }) test('the purchase URL can be updated on an existing component', async ({ client, assert }) => { const owner = await createUser('purchase-url-d@example.com') const type = await createType('RAM-purchase-4') const component = await createComponent(owner.id, type.id) const response = await client .put(`/components/${component.id}`) .loginAs(owner) .withCsrfToken() .redirects(0) .json({ name: component.name, price: component.price, typeId: type.id, purchaseDate: DateTime.now().toFormat('yyyy-MM-dd'), purchaseUrl: 'https://fr.pc-builder.io/cpu/example-p2', }) response.assertStatus(302) await component.refresh() assert.equal(component.purchaseUrl, 'https://fr.pc-builder.io/cpu/example-p2') }) })