From 74263b1e1c4cf8590f41b46ef5b79504b70b8e92 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 28 Aug 2025 22:43:21 +0200 Subject: [PATCH 1/4] add migration command to package.json --- Dockerfile | 2 +- package.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 10706ec..42d2c5c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:latest +FROM debian:12 # Install dependencies RUN apt-get update diff --git a/package.json b/package.json index a1170cb..cd0889f 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "start": "node src/app.js", "start:container": "docker compose up -d --build", "stop:container": "docker compose down", + "migrate": "npx sequelize-cli db:migrate", "build": "webpack", "watch": "webpack --watch", "cli": "node src/cli.js", From f444bd792fb6ab56dbced7d63a588bf792a29904 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 28 Aug 2025 22:45:36 +0200 Subject: [PATCH 2/4] remove tests --- package.json | 1 - tests/createOffer.spec.js | 106 ----------------------------------- tests/recorderHelper.spec.js | 27 --------- tests/test-setup.js | 72 ------------------------ 4 files changed, 206 deletions(-) delete mode 100644 tests/createOffer.spec.js delete mode 100644 tests/recorderHelper.spec.js delete mode 100644 tests/test-setup.js diff --git a/package.json b/package.json index cd0889f..2c904f5 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "build": "webpack", "watch": "webpack --watch", "cli": "node src/cli.js", - "test": "playwright test", "lint": "eslint . --fix", "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,html,ejs}\"" }, diff --git a/tests/createOffer.spec.js b/tests/createOffer.spec.js deleted file mode 100644 index 2820b3a..0000000 --- a/tests/createOffer.spec.js +++ /dev/null @@ -1,106 +0,0 @@ -const { test, expect, hardcodedSessionUuid } = require('./test-setup'); - -const SessionCreated = require('../src/models/SessionCreated'); -const SessionRelatedToPublickey = require('../src/models/SessionRelatedToPublickey'); -const NymSet = require('../src/models/NymSet'); -const ContactDetailsSet = require('../src/models/ContactDetailsSet'); -const OfferDetailsSet = require('../src/models/OfferDetailsSet'); - -test('Mock records are present', async () => { - for (const someModel of [ - SessionCreated, - SessionRelatedToPublickey, - NymSet, - ContactDetailsSet, - ]) { - expect(await someModel.findOne()).toBeTruthy(); - } -}); - -test('Hardcoded session cookie is there', async ({ context }) => { - const page = await context.newPage(); - const cookiesInPage = await page.context().cookies(); - expect(cookiesInPage).toHaveLength(1); - expect(cookiesInPage[0].name).toBe('sessionUuid'); - expect(cookiesInPage[0].value).toBe(hardcodedSessionUuid); -}); - -test('Offers is reachable', async ({ context }) => { - const page = await context.newPage(); - - await page.goto('http://localhost/offers'); - const createOfferButton = page.locator('#button-start-create-offer'); - await expect(createOfferButton).toBeVisible(); - await expect(createOfferButton).toContainText('Crear nueva oferta'); -}); - -test('Create an offer with a few options creates in DB', async ({ - context, -}) => { - const page = await context.newPage(); - await page.goto('http://localhost/offers'); - - await page.getByRole('button', { name: 'Crear nueva oferta' }).click(); - await expect(page.locator('#close-offer-controls-area')).toBeVisible(); - await page.getByRole('button', { name: 'Quiero vender Bitcoin' }).click(); - await page.getByRole('button', { name: 'Quiero comprar Bitcoin' }).click(); - await page.getByRole('button', { name: '+' }).click(); - await page.getByRole('button', { name: '+' }).click(); - await page.getByRole('button', { name: '+' }).click(); - await page.getByRole('button', { name: '-' }).click(); - await expect(page.locator('#premium-value')).toContainText('2%'); - await page.locator('#input-eur-amount').click(); - await page.locator('#input-eur-amount').press('ControlOrMeta+a'); - await page.locator('#input-eur-amount').fill('50'); - await expect(page.locator('#input-eur-amount')).toHaveValue('50'); - await page - .getByText( - 'Añade los detalles de tu oferta Quiero comprar Bitcoin Quiero vender Bitcoin' - ) - .click(); - await page - .getByRole('textbox', { name: '¿Dónde? Ej."Eixample", "La' }) - .click(); - await page - .getByRole('textbox', { name: '¿Dónde? Ej."Eixample", "La' }) - .fill('En algún lugar'); - await page - .getByRole('textbox', { name: '¿Cuándo? Ej."Cualquier hora' }) - .click(); - await page - .getByRole('textbox', { name: '¿Cuándo? Ej."Cualquier hora' }) - .fill('En algún momento'); - await page.locator('#onchain-checkbox').uncheck(); - await expect(page.locator('#onchain-checkbox')).not.toBeChecked(); - await expect(page.locator('#lightning-checkbox')).toBeChecked(); - await page.locator('#my-trusted-trusted-checkbox').uncheck(); - await page.locator('#all-members-checkbox').check(); - await page.locator('#my-trusted-trusted-checkbox').check(); - await expect(page.locator('#my-trusted-trusted-checkbox')).toBeChecked(); - await page.locator('#all-members-checkbox').uncheck(); - await expect(page.locator('#all-members-checkbox')).not.toBeChecked(); - await page.locator('#large-bills-checkbox').check(); - await expect(page.locator('#large-bills-checkbox')).toBeChecked(); - - await page.getByRole('button', { name: 'Publicar oferta' }).click(); - await expect(page.locator('#offer-created-confirmation')).toBeInViewport(); - await expect( - page.locator('#offer-created-confirmation') - ).not.toBeInViewport(); - await expect(page.locator('#close-offer-controls-area')).not.toBeVisible(); - - const createdOfferDetailsSetRecord = await OfferDetailsSet.findOne(); - expect(createdOfferDetailsSetRecord.wants).toBe('BTC'); - expect(createdOfferDetailsSetRecord.premium).toBe('0.02'); - expect(createdOfferDetailsSetRecord.trade_amount_eur).toBe(50); - expect(createdOfferDetailsSetRecord.location_details).toBe('En algún lugar'); - expect(createdOfferDetailsSetRecord.time_availability_details).toBe( - 'En algún momento' - ); - expect(createdOfferDetailsSetRecord.show_offer_to_trusted).toBe(true); - expect(createdOfferDetailsSetRecord.show_offer_to_trusted_trusted).toBe(true); - expect(createdOfferDetailsSetRecord.show_offer_to_all_members).toBe(false); - expect(createdOfferDetailsSetRecord.is_onchain_accepted).toBe(false); - expect(createdOfferDetailsSetRecord.is_lightning_accepted).toBe(true); - expect(createdOfferDetailsSetRecord.are_big_notes_accepted).toBe(true); -}); diff --git a/tests/recorderHelper.spec.js b/tests/recorderHelper.spec.js deleted file mode 100644 index 2c87c21..0000000 --- a/tests/recorderHelper.spec.js +++ /dev/null @@ -1,27 +0,0 @@ -// You can uncomment this below to open a recorder page - -/* -const { chromium } = require('playwright'); -test('Mock records are present', async () => { - const browser = await chromium.launch({ headless: false }); - const context = await browser.newContext(); - await context.addCookies([ - { - name: 'sessionUuid', - value: hardcodedSessionUuid, - domain: 'localhost', - path: '/', - expires: Math.floor( - new Date(new Date().setMonth(new Date().getMonth() + 1)).getTime() / - 1000 - ), //This monster is this day next month, turned into epoch format - httpOnly: true, - secure: false, - sameSite: 'Lax', - }, - ]); - - const page = await context.newPage(); - await page.goto('http://localhost'); -}); -*/ diff --git a/tests/test-setup.js b/tests/test-setup.js deleted file mode 100644 index 3d4b669..0000000 --- a/tests/test-setup.js +++ /dev/null @@ -1,72 +0,0 @@ -const { test, expect } = require('@playwright/test'); - -const SessionCreated = require('../src/models/SessionCreated'); -const SessionRelatedToPublickey = require('../src/models/SessionRelatedToPublickey'); -const NymSet = require('../src/models/NymSet'); -const ContactDetailsSet = require('../src/models/ContactDetailsSet'); - -const hardcodedSessionUuid = '0195423c-33d7-75f8-921b-a06e6d3cb8c5'; -const hardcodedPublicKey = - 'd3d4c49e7bdbbbf3082151add080e92f9a458d5dec993b371fe6d02cd394d57a'; - -test.beforeEach(async () => { - for (const someModel of [ - SessionCreated, - SessionRelatedToPublickey, - NymSet, - ContactDetailsSet, - ]) { - someModel.truncate(); - } - - const currentTimestamp = new Date(); - const expiryTimestamp = new Date(currentTimestamp.getTime()); - expiryTimestamp.setSeconds(expiryTimestamp.getSeconds() + 60); - await SessionCreated.create({ - uuid: hardcodedSessionUuid, - created_at: currentTimestamp.toISOString(), - expires_at: expiryTimestamp.toISOString(), - }); - - await SessionRelatedToPublickey.create({ - uuid: '0195423b-f9ae-737e-98f3-880f6563ed8a', - session_uuid: hardcodedSessionUuid, - public_key: hardcodedPublicKey, - created_at: new Date().toISOString(), - }); - - await NymSet.create({ - uuid: '01954240-ddbb-7d01-9017-efb3e500d333', - public_key: hardcodedPublicKey, - nym: 'test_nym', - created_at: new Date().toISOString(), - }); - - await ContactDetailsSet.create({ - uuid: '01954240-ddbb-7d01-9017-efb3e500d333', - public_key: hardcodedPublicKey, - encrypted_contact_details: - '+OD0/Y2IkJ99/E0KAJL/mp3kxQo4DFp1deSPnqiejlyGoeWzBiipemPVSTT/Jg/fCQbN9Pd/GJ6shxuwWECOVyB5PnMZOVJ1MPQ7I8A+63XZ0gKnSnJgry6F69f3MhEjH49JbeVJ37TbruFu/Woevo24VWz2gPXGBuyHLzeg1tyT9+7ZSygkcCrh+bchvymCoF1nNOm/UQKnwecH1wWzo8a+rNokazD1/3iey6iKmKewi+yGCgmljrB866akqBAl?iv=PAKhqTeBfYVX/muhM8xaEA==', - created_at: new Date().toISOString(), - }); -}); - -test.beforeEach(async ({ context }) => { - await context.addCookies([ - { - name: 'sessionUuid', - value: hardcodedSessionUuid, - domain: 'localhost', - path: '/', - expires: Math.floor( - new Date(new Date().setMonth(new Date().getMonth() + 1)).getTime() / - 1000 - ), //This monster is this day next month, turned into epoch format - httpOnly: true, - secure: false, - sameSite: 'Lax', - }, - ]); -}); - -module.exports = { test, expect, hardcodedSessionUuid }; From cd9c7678eeac5753d4eaad7811b0e4264e120153 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 28 Aug 2025 22:49:07 +0200 Subject: [PATCH 3/4] add AGENTS.md --- AGENTS.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..fa244c9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,10 @@ +# secajs + +## Architecture + +This repository contains a webapp. It covers the full stack which consists of: +- The client side code, in `src/front/`. +- The backend service in `src/`, except for `src/front/`. +- A Postgres database. Database connections and migrations are in `src/database/`. +- Besides, there is an admin CLI, with entrypoint in `src/cli.js` and commands in `src/commands/`. + From 1a5ef88c55cdaecf2d30bf241ba9c2f33020b635 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 28 Aug 2025 22:57:20 +0200 Subject: [PATCH 4/4] basic tests --- package.json | 3 ++- playwright.config.js | 13 +++++++++++++ tests/basic.spec.js | 12 ++++++++++++ tests/invite.spec.js | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 playwright.config.js create mode 100644 tests/basic.spec.js create mode 100644 tests/invite.spec.js diff --git a/package.json b/package.json index 2c904f5..568a109 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "watch": "webpack --watch", "cli": "node src/cli.js", "lint": "eslint . --fix", - "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,html,ejs}\"" + "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,html,ejs}\"", + "test": "playwright test" }, "keywords": [], "author": "", diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..c7f57c8 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,13 @@ +const { defineConfig } = require('@playwright/test'); + +module.exports = defineConfig({ + testDir: './tests', + use: { + baseURL: 'http://localhost:3000', + }, + webServer: { + command: 'npm start', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/tests/basic.spec.js b/tests/basic.spec.js new file mode 100644 index 0000000..3c51e92 --- /dev/null +++ b/tests/basic.spec.js @@ -0,0 +1,12 @@ +const { test, expect } = require('@playwright/test'); + +test('app starts and public page is reachable', async ({ page }) => { + // Navigate to the root page + await page.goto('/'); + + // Check that the page loads (should redirect to login) + await expect(page).toHaveURL('/login'); + + // Verify we can see some content on the login page + await expect(page.locator('body')).toBeVisible(); +}); diff --git a/tests/invite.spec.js b/tests/invite.spec.js new file mode 100644 index 0000000..d49cde1 --- /dev/null +++ b/tests/invite.spec.js @@ -0,0 +1,36 @@ +const { test, expect } = require('@playwright/test'); +const { execSync } = require('child_process'); + +test('can create invite with CLI and access invite page', async ({ page }) => { + // Create an invite using the CLI + const inviterNpub = 'npub1test1234567890abcdefghijklmnopqrstuvwxyz'; + + try { + const output = execSync(`npm run cli createAppInvite ${inviterNpub}`, { + encoding: 'utf8', + cwd: process.cwd() + }); + + // Extract the invite UUID from the CLI output + const match = output.match(/http:\/\/localhost\/invite\/([a-f0-9-]+)/); + if (!match) { + throw new Error('Could not extract invite UUID from CLI output'); + } + + const inviteUuid = match[1]; + console.log(`Created invite with UUID: ${inviteUuid}`); + + // Navigate to the invite page + await page.goto(`/invite/${inviteUuid}`); + + // Check that the invite page loads correctly + await expect(page).toHaveTitle('Invite Details'); + await expect(page.locator('h1')).toContainText('¡Has sido invitado a la seca!'); + await expect(page.locator('#laseca-logo')).toBeVisible(); + await expect(page.locator('#nostr-signup-button')).toBeVisible(); + + } catch (error) { + console.error('Error creating invite or accessing page:', error); + throw error; + } +});