Compare commits
4 commits
offer-crea
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a5ef88c55 | |||
| cd9c7678ee | |||
| f444bd792f | |||
| 74263b1e1c |
9 changed files with 75 additions and 208 deletions
10
AGENTS.md
Normal file
10
AGENTS.md
Normal file
|
|
@ -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/`.
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
FROM debian:latest
|
||||
FROM debian:12
|
||||
|
||||
# Install dependencies
|
||||
RUN apt-get update
|
||||
|
|
|
|||
|
|
@ -20,12 +20,13 @@
|
|||
"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",
|
||||
"test": "playwright test",
|
||||
"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": "",
|
||||
|
|
|
|||
13
playwright.config.js
Normal file
13
playwright.config.js
Normal file
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
12
tests/basic.spec.js
Normal file
12
tests/basic.spec.js
Normal file
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
36
tests/invite.spec.js
Normal file
36
tests/invite.spec.js
Normal file
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
|
@ -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');
|
||||
});
|
||||
*/
|
||||
|
|
@ -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 };
|
||||
Loading…
Add table
Add a link
Reference in a new issue