bmc_hub/tests/browser/economic_catalog.cjs
Christian 631fc6cdca Implement four-eyes approval process for e-conomic changes
- Added migration to create economic_change_requests table for tracking change requests for customers and products.
- Introduced new permissions for requesting and approving changes in e-conomic.
- Developed frontend JavaScript functionality for managing economic catalog, including handling change requests and displaying their statuses.
- Created browser tests to validate UI interactions related to product management and change requests.
- Added unit tests for backend logic to ensure proper handling of product numbers, price rules, and economic write policies.
2026-09-13 02:30:52 +02:00

66 lines
4.7 KiB
JavaScript

/* Isolated UI test: every request is intercepted; no Hub/e-conomic data is changed.
* BMC_PLAYWRIGHT_MODULE=/path/to/playwright-core node tests/browser/economic_catalog.cjs
*/
const assert = require('node:assert/strict');
const fs = require('node:fs');
const {chromium} = require(process.env.BMC_PLAYWRIGHT_MODULE || 'playwright-core');
(async () => {
const browser = await chromium.launch({headless:true, executablePath:process.env.BMC_CHROME_EXECUTABLE || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});
const page = await browser.newPage({viewport:{width:1440,height:1000}});
const errors = [], writes = [];
page.on('pageerror', e=>errors.push(e.message));
const html = '<!doctype html><meta charset="utf-8">' + fs.readFileSync('app/products/frontend/economic.html','utf8')
.replace(/{%[^%]*%}/g,'');
const script = fs.readFileSync('static/js/economic-catalog.js','utf8');
const permissions = Object.fromEntries(['economic.catalog.manage','economic.catalog.rebind','economic.pricing.manage','economic.documents.export','products.update'].map(k=>[k,true]));
let products = [{id:1,name:'Microsoft 365',economic_product_number:null,economic_sync_status:'unlinked',sales_price:100,sales_currency:'DKK',lifecycle_status:'active'}];
const connection = {id:1,name:'Testaftale',agreement_number:'123',currency:'DKK',enabled:false,auto_sync:false,number_prefix:'TEST-',sync_interval_minutes:15};
await page.route('**/*', async route=>{
const url = new URL(route.request().url()), path=url.pathname;
if (path === '/') return route.fulfill({contentType:'text/html',body:html});
if (path === '/static/js/economic-catalog.js') return route.fulfill({contentType:'application/javascript',body:script});
if (route.request().method() !== 'GET') writes.push({path,body:route.request().postDataJSON()});
let data = {};
if(path.endsWith('/status'))data={connections:[connection],permissions,read_only:true,dry_run:true,jobs:[],exports:[]};
else if(path.endsWith('/references'))data=[{kind:'product-groups',number:1,name:'Software',allow_new:true},{kind:'units',number:1,name:'stk'}];
else if(path === '/api/v1/economic/catalog/products')data=products;
else if(path.endsWith('/economic-link')) { products=[{...products[0],economic_product_number:'00123',economic_sync_status:'synced'}];data={linked:true}; }
else if(path.endsWith('/price-rules') && route.request().method()==='GET')data=[];
return route.fulfill({contentType:'application/json',body:JSON.stringify(data)});
});
try {
await page.goto('http://catalog.test/');
await page.getByRole('button',{name:'Åbn',exact:true}).click();
await page.locator('input[name=number]').fill('00123');
// Linking must work without selecting group/unit (only needed for creation).
await page.getByRole('button',{name:'Gem',exact:true}).click();
await page.locator('#ecoDialog').waitFor({state:'hidden'});
assert(writes.some(w=>w.path.endsWith('/economic-link')&&w.body.number==='00123'));
await page.getByRole('button',{name:'Pris',exact:true}).click();
await page.locator('input[name=sales_price]').fill('123.45');
await page.getByRole('button',{name:'Gem',exact:true}).click();
await page.locator('#ecoDialog').waitFor({state:'hidden'});
assert(writes.some(w=>w.path.endsWith('/economic-pricing')&&w.body.sales_price==='123.45'));
await page.getByRole('button',{name:'Status',exact:true}).click();
await page.locator('select[name=lifecycle_status]').selectOption('phasing_out');
await page.getByRole('button',{name:'Gem',exact:true}).click();
await page.locator('#ecoDialog').waitFor({state:'hidden'});
assert(writes.some(w=>w.path.endsWith('/economic-state')&&w.body.lifecycle_status==='phasing_out'));
await page.getByRole('button',{name:'Prisregler',exact:true}).click();
await page.getByRole('button',{name:'Ny prisregel',exact:true}).click();
await page.locator('input[name=name]').fill('Test rabat');
await page.locator('input[name=value]').fill('12.5');
await page.getByRole('button',{name:'Gem',exact:true}).click();
await page.locator('#ecoDialog').waitFor({state:'hidden'});
assert(writes.some(w=>w.path.endsWith('/price-rules')&&w.body.value===12.5));
await page.getByRole('button',{name:'Sync og fejl',exact:true}).click();
assert(await page.getByRole('button',{name:'Eksportér gemt ordre som kladde'}).isVisible());
assert.deepEqual(errors, []);
console.log('PASS: link without group/unit, decimal price, lifecycle, price rule, exports tab; zero JS errors');
} catch (error) {
console.error('UI errors:', errors, 'Feedback:', await page.locator('#ecoFeedback').textContent().catch(()=>''));
throw error;
} finally { await browser.close(); }
})().catch(error=>{console.error(error);process.exitCode=1;});