From 71e5fb8fda50d4cde57ee5573b3fae68e31dd27d Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Sat, 2 May 2026 21:47:20 +0300 Subject: [PATCH] add cypress coverage for user text pages loginAsSecondUser helper backs new specs that cover the /texts list (own-only scoping, create form, link to /texts/{id}) and /texts/{id} detail (own access, 403 on another user's text, owner can add a child node). --- cypress/e2e/userText.cy.js | 57 +++++++++++++++++++++++++++++++++++++ cypress/e2e/userTexts.cy.js | 53 ++++++++++++++++++++++++++++++++++ cypress/support/commands.js | 4 +++ 3 files changed, 114 insertions(+) create mode 100644 cypress/e2e/userText.cy.js create mode 100644 cypress/e2e/userTexts.cy.js diff --git a/cypress/e2e/userText.cy.js b/cypress/e2e/userText.cy.js new file mode 100644 index 0000000..a633e5a --- /dev/null +++ b/cypress/e2e/userText.cy.js @@ -0,0 +1,57 @@ +describe('The user text detail page', () => { + beforeEach(() => { + cy.exec('npm run db:seed') + }) + afterEach(() => { + cy.exec('npm run db:wipe') + }) + + it('renders own text with heading', () => { + cy.loginAsUser() + cy.intercept('GET', '/api/texts/0').as('getText') + cy.visit('/texts/0') + cy.wait('@getText') + cy.get('h1').should('contain', 'Tanach') + }) + + it('returns 403 when accessing another user text', () => { + cy.loginAsSecondUser() + cy.request({ + url: '/api/texts/0', + failOnStatusCode: false, + }).then((response) => { + expect(response.status).to.eq(403) + }) + }) + + it('owner can add a child node', () => { + cy.loginAsUser() + cy.intercept('GET', '/api/nodes/0').as('getNodes') + cy.visit('/texts/0') + cy.wait('@getNodes') + + cy.get('#text-detail li').first().within(() => { + cy.get('button.add-child').click() + cy.get('input.child-title').type('My new child') + cy.get('button.save-child').click() + }) + + cy.contains('My new child') + }) + + it('non-owner gets 403 when posting a node to that text', () => { + cy.loginAsSecondUser() + cy.request({ + method: 'POST', + url: '/api/nodes', + body: { + textId: 0, + title: 'Hijack', + parentNodeId: 0, + }, + failOnStatusCode: false, + }).then((response) => { + expect(response.status).to.eq(403) + }) + }) +}) diff --git a/cypress/e2e/userTexts.cy.js b/cypress/e2e/userTexts.cy.js new file mode 100644 index 0000000..c1ad25c --- /dev/null +++ b/cypress/e2e/userTexts.cy.js @@ -0,0 +1,53 @@ +describe('The user texts page', () => { + beforeEach(() => { + cy.exec('npm run db:seed') + cy.loginAsUser() + }) + afterEach(() => { + cy.exec('npm run db:wipe') + }) + + it('shows my texts page with heading and form', () => { + cy.visit('/texts') + cy.get('h1').should('contain', 'My Texts') + cy.get('#newTextName').should('exist') + cy.get('#submit').should('exist') + }) + + it('lists the seeded text owned by the user', () => { + cy.intercept('GET', '/api/texts').as('getTexts') + cy.visit('/texts') + cy.wait('@getTexts') + cy.get('#texts-list').should('contain', 'Tanach') + }) + + it('creates a new text', () => { + cy.visit('/texts') + cy.get('#newTextName').type('My Notes') + cy.get('#submit').click() + cy.contains('My Notes') + }) + + it('newly created text links to /texts/{id}', () => { + cy.visit('/texts') + cy.get('#newTextName').type('Linked Text') + cy.get('#submit').click() + cy.get('a') + .contains('Linked Text') + .should('have.attr', 'href') + .and('match', /^\/texts\/\d+$/) + }) + + it('does not show texts owned by other users', () => { + cy.loginAsSecondUser() + cy.visit('/texts') + cy.get('#texts-list').should('not.contain', 'Tanach') + }) + + it('navigates to user text detail on click', () => { + cy.visit('/texts') + cy.get('a').contains('Tanach').click() + cy.url().should('match', /\/texts\/0$/) + cy.get('#back').should('have.attr', 'href', '/texts') + }) +}) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 61e6549..1c1d028 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -13,3 +13,7 @@ Cypress.Commands.add('loginAsAdmin', () => { Cypress.Commands.add('loginAsUser', () => { cy.login('user@example.com', 'password1') }) + +Cypress.Commands.add('loginAsSecondUser', () => { + cy.login('user2@example.com', 'password2') +})