Added OIDC workflow and Helm Chart
Checks / test (push) Failing after 4m13s

This commit is contained in:
Aleksandr Bogomiakov committed 2026-10-07 14:22:37 +01:00
1 parent 984c7083a1
commit 7afdc4be97
29 files changed
+872 -33

No files matched your search

+92
View File
@@ -692,6 +692,7 @@ async fn only_instance_administrators_manage_password_users_and_delete_disabled_
password: "long-enough-password".into(),
email: None,
groups: vec![],
country: None,
};
// Nothing is available to ordinary users, not even read access to the overview.
@@ -1079,6 +1080,7 @@ async fn taken_username(db: &Store, config: &Config) {
password: "long-enough-password".into(),
email: None,
groups: vec![],
country: None,
},
)
.await
@@ -1265,3 +1267,93 @@ async fn first_sign_in_links_the_email_contact_everywhere_and_never_grants_acces
);
iso.drop().await;
}
#[tokio::test]
#[ignore = "requires MEGURI_TEST_DATABASE_URL"]
async fn employees_plan_schedules_but_only_administrators_change_preferences_and_the_directory() {
let f = support::Fixture::new().await;
let (lead, bob) = (f.people[0].id, f.people[1].id);
let team = f.team.id;
// Employees may edit schedules here, as with the default employee permission set.
let record = service::set_member_permissions(
&f.db,
lead,
team,
f.team.revision,
Permission::employee_default(),
)
.await
.unwrap();
let contact = Uuid::new_v4();
let mut doc = record.document.clone();
doc.people.push(Person {
id: contact,
user_id: None,
display_name: "Dana Contact".into(),
email: Some("dana@corp.test".into()),
country: Some("AM".into()),
aliases: vec![],
});
let record = service::update_document(&f.db, lead, team, record.revision, doc)
.await
.unwrap();
let try_edit = |change: &dyn Fn(&mut WorkspaceDocument)| {
let mut doc = record.document.clone();
change(&mut doc);
service::update_document(&f.db, bob, team, record.revision, doc)
};
// Workspace preferences and existing directory entries are administrator-only.
for (label, change) in [
(
"name",
&(|d: &mut WorkspaceDocument| d.name = "Renamed".into())
as &dyn Fn(&mut WorkspaceDocument),
),
("horizon", &|d| d.horizon_days = 30),
("person name", &|d| {
d.people[0].display_name = "Someone else".into()
}),
("person email", &|d| {
d.people[0].email = Some("other@corp.test".into())
}),
("person country", &|d| {
d.people[0].country = Some("CY".into())
}),
("person removal", &|d| d.people.clear()),
] {
assert!(
matches!(try_edit(change).await, Err(Error::Forbidden)),
"an employee must not change the {label}"
);
}
// Planning is allowed, including new email contacts and unchanged people sent back as is.
let planned = try_edit(&|d| {
d.schedules[0].name = "Weekly rotation".into();
d.people.push(Person {
id: Uuid::new_v4(),
user_id: None,
display_name: "New contact".into(),
email: Some("new@corp.test".into()),
country: None,
aliases: vec![],
});
d.people[0].email = Some(" DANA@corp.test ".into());
})
.await
.unwrap();
assert_eq!(planned.document.schedules[0].name, "Weekly rotation");
assert_eq!(planned.document.name, record.document.name);
assert_eq!(planned.document.people.len(), 2);
// The workspace administrator still changes both.
let mut doc = planned.document.clone();
doc.name = "Administrators rename".into();
doc.horizon_days = 45;
doc.people[0].display_name = "Dana Renamed".into();
let changed = service::update_document(&f.db, lead, team, planned.revision, doc)
.await
.unwrap();
assert_eq!(changed.document.name, "Administrators rename");
assert_eq!(changed.document.horizon_days, 45);
assert_eq!(changed.document.people[0].display_name, "Dana Renamed");
}
+5
View File
@@ -142,6 +142,11 @@ fs.mkdirSync('test-results', { recursive: true });
const detail = await carol.get('/api/workspaces/' + commerceId);
const denied = await carol.call('PUT', `/api/workspaces/${commerceId}/member-permissions`, { revision: detail.workspace.revision, permissions: ['view', 'force_overrides', 'edit_schedules'] });
assert.equal(denied.status, 403);
// Employees may plan schedules, but workspace preferences and the scheduling directory are read-only.
assert.equal(await worker.getByRole('button', { name: 'Edit preferences', exact: true }).count(), 0, 'Employees cannot edit workspace preferences');
assert.equal(await worker.getByRole('button', { name: 'Edit person', exact: true }).count(), 0, 'Employees cannot edit the scheduling directory');
const rename = await carol.call('PUT', `/api/workspaces/${commerceId}/config`, { revision: detail.workspace.revision, document: { ...detail.workspace.document, name: 'Renamed by an employee' } });
assert.equal(rename.status, 403);
await worker.screenshot({ path: 'test-results/access-employee.png', fullPage: true });
// The change applies at once to everyone: turning it off removes the permission without a new login.
+1 -1
View File
@@ -1,2 +1,2 @@
const assert=require('node:assert/strict');
(async()=>{const browser=await require('./support/browser.cjs').launchBrowser();try{const page=await browser.newPage({viewport:{width:1440,height:1050}}),errors=[];page.on('pageerror',e=>errors.push(e.message));await page.goto('http://127.0.0.1:3000/?view=configuration');await page.getByLabel('Username',{exact:true}).fill('alice');await page.getByLabel('Password',{exact:true}).fill(await require('./support/browser.cjs').demoPassword());await page.getByRole('button',{name:'Sign in',exact:true}).click();await page.getByRole('button',{name:'Edit rule Weekly Time off',exact:true}).waitFor();await page.waitForFunction(()=>{const a=Alpine.$data(document.body);return a.generationResult&&!a.generationLoading;});assert.deepEqual(await page.evaluate(()=>Alpine.$data(document.body).generationResult.issues),[]);assert.equal(await page.locator('.generation-review').getAttribute('open'),null);assert.equal(await page.locator('#public-holidays').getAttribute('open'),null);assert.equal((await page.locator('#public-holidays > .widget-heading .badge').innerText()).trim(),'33');assert.equal(await page.locator('.setup-jumps').count(),0);assert.equal(await page.getByText('Start with projects & responsible example',{exact:true}).count(),0);assert.equal(await page.locator('.resource-row[open]').count(),0);assert(await page.locator('.resource-row.generated-resource').count()>0);assert.equal(await page.locator('.resource-create > button').count(),2);assert(await page.locator('#availability').evaluate(el=>el.getBoundingClientRect().left > document.querySelector('.generation-panel:nth-child(2)').getBoundingClientRect().left));const placement=page.locator('.generated-routing-placement');assert(!await placement.isVisible());await page.evaluate(()=>{const a=Alpine.$data(document.body);a.doc.routes.push({id:'placement-test-b',name:'Catch all',order:200,conditions:[]},{id:'placement-test-a',name:'Critical alerts',order:100,conditions:[]});});await placement.waitFor({state:'visible'});assert.deepEqual(await placement.locator('option').allTextContents(),['Before manual rules','After “Critical alerts”','After “Catch all”']);await placement.locator('select').selectOption('1');assert.equal(await page.evaluate(()=>Alpine.$data(document.body).generationDraft.route_position),1);await page.evaluate(()=>{const a=Alpine.$data(document.body);a.doc.routes=a.doc.routes.filter(r=>!r.id.startsWith('placement-test-'));a.generationDraft.route_position=0;});await placement.waitFor({state:'hidden'});const row=page.locator('.resource-row').first();await row.locator('summary').click();await row.locator('.resource-body').waitFor();await row.locator('summary').click();await page.locator('.schedule-create > summary').click();await page.getByRole('button',{name:'+ Rotation',exact:true}).click();await page.getByRole('dialog').waitFor();await page.getByRole('dialog').getByRole('button',{name:'Cancel',exact:true}).click();const overview=await page.evaluate(()=>{const a=Alpine.$data(document.body);return a.generationDraft.rules.map(r=>a.ruleOverview(r));});assert(overview.some(t=>t.includes('{ var.projects }')&&t.includes('Escalation')));assert(!overview[0].includes('Iterations'));for(const kind of ['schedules','escalations','routes']){const resource=page.locator('[data-resource-kind="'+kind+'"] .resource-summary').first();assert((await resource.locator('.resource-brief').innerText()).length>10);await resource.click();const link=resource.locator('..').locator('.source-rule-link');const name=(await link.innerText()).replace('Generated by: ','');await link.click();await page.waitForFunction(name=>Alpine.$data(document.body).form.name===name,name);await page.getByRole('dialog').getByRole('button',{name:'Cancel',exact:true}).click();await resource.click();}await page.evaluate(()=>window.scrollTo(0,0));await page.screenshot({path:'test-results/setup-desktop.png',fullPage:true});const japan=page.locator('.holiday-group').filter({has:page.locator('summary').filter({hasText:'Japan'})});assert.equal(await japan.getAttribute('open'),null);assert.equal((await japan.locator('.badge').innerText()).trim(),'18');await japan.locator('summary').click();await japan.getByText('Coming of Age Day',{exact:true}).waitFor();await page.locator('#public-holidays').screenshot({path:'test-results/setup-holidays.png'});await page.getByRole('button',{name:'Import CSV',exact:true}).click();await page.getByRole('dialog').waitFor();await page.getByRole('dialog').getByRole('button',{name:'Cancel',exact:true}).click();await page.setViewportSize({width:390,height:844});await page.evaluate(()=>window.scrollTo(0,0));await page.screenshot({path:'test-results/setup-mobile.png',fullPage:true});assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));assert.deepEqual(errors,[]);console.log('PASS setup layout, collapsed review, 33 country holidays, CSV modal and mobile');}finally{await browser.close();}})().catch(e=>{console.error(e);process.exitCode=1;});
(async()=>{const browser=await require('./support/browser.cjs').launchBrowser();try{const page=await browser.newPage({viewport:{width:1440,height:1050}}),errors=[];page.on('pageerror',e=>errors.push(e.message));await page.goto('http://127.0.0.1:3000/?view=configuration');await page.getByLabel('Username',{exact:true}).fill('alice');await page.getByLabel('Password',{exact:true}).fill(await require('./support/browser.cjs').demoPassword());await page.getByRole('button',{name:'Sign in',exact:true}).click();await page.getByRole('button',{name:'Edit rule Weekly Time off',exact:true}).waitFor();await page.waitForFunction(()=>{const a=Alpine.$data(document.body);return a.generationResult&&!a.generationLoading;});assert.deepEqual(await page.evaluate(()=>Alpine.$data(document.body).generationResult.issues),[]);assert.equal(await page.locator('.generation-review').getAttribute('open'),null);assert.equal(await page.locator('#public-holidays').getAttribute('open'),null);assert.equal((await page.locator('#public-holidays > .widget-heading .badge').innerText()).trim(),'36');assert.equal(await page.locator('.setup-jumps').count(),0);assert.equal(await page.getByText('Start with projects & responsible example',{exact:true}).count(),0);assert.equal(await page.locator('.resource-row[open]').count(),0);assert(await page.locator('.resource-row.generated-resource').count()>0);assert.equal(await page.locator('.resource-create > button').count(),2);assert(await page.locator('#availability').evaluate(el=>el.getBoundingClientRect().left > document.querySelector('.generation-panel:nth-child(2)').getBoundingClientRect().left));const placement=page.locator('.generated-routing-placement');assert(!await placement.isVisible());await page.evaluate(()=>{const a=Alpine.$data(document.body);a.doc.routes.push({id:'placement-test-b',name:'Catch all',order:200,conditions:[]},{id:'placement-test-a',name:'Critical alerts',order:100,conditions:[]});});await placement.waitFor({state:'visible'});assert.deepEqual(await placement.locator('option').allTextContents(),['Before manual rules','After “Critical alerts”','After “Catch all”']);await placement.locator('select').selectOption('1');assert.equal(await page.evaluate(()=>Alpine.$data(document.body).generationDraft.route_position),1);await page.evaluate(()=>{const a=Alpine.$data(document.body);a.doc.routes=a.doc.routes.filter(r=>!r.id.startsWith('placement-test-'));a.generationDraft.route_position=0;});await placement.waitFor({state:'hidden'});const row=page.locator('.resource-row').first();await row.locator('summary').click();await row.locator('.resource-body').waitFor();await row.locator('summary').click();await page.locator('.schedule-create > summary').click();await page.getByRole('button',{name:'+ Rotation',exact:true}).click();await page.getByRole('dialog').waitFor();await page.getByRole('dialog').getByRole('button',{name:'Cancel',exact:true}).click();const overview=await page.evaluate(()=>{const a=Alpine.$data(document.body);return a.generationDraft.rules.map(r=>a.ruleOverview(r));});assert(overview.some(t=>t.includes('{ var.projects }')&&t.includes('Escalation')));assert(!overview[0].includes('Iterations'));for(const kind of ['schedules','escalations','routes']){const resource=page.locator('[data-resource-kind="'+kind+'"] .resource-summary').first();assert((await resource.locator('.resource-brief').innerText()).length>10);await resource.click();const link=resource.locator('..').locator('.source-rule-link');const name=(await link.innerText()).replace('Generated by: ','');await link.click();await page.waitForFunction(name=>Alpine.$data(document.body).form.name===name,name);await page.getByRole('dialog').getByRole('button',{name:'Cancel',exact:true}).click();await resource.click();}await page.evaluate(()=>window.scrollTo(0,0));await page.screenshot({path:'test-results/setup-desktop.png',fullPage:true});for(const [country,count] of [['Cyprus','15'],['United Kingdom','8'],['Armenia','13']]){const group=page.locator('.holiday-group').filter({has:page.locator('summary').filter({hasText:country})});assert.equal(await group.getAttribute('open'),null,country);assert.equal((await group.locator('.badge').innerText()).trim(),count,country);}const armenia=page.locator('.holiday-group').filter({has:page.locator('summary').filter({hasText:'Armenia'})});await armenia.locator('summary').click();await armenia.getByText('Independence Day',{exact:true}).waitFor();await page.locator('#public-holidays').screenshot({path:'test-results/setup-holidays.png'});await page.getByRole('button',{name:'Import CSV',exact:true}).click();await page.getByRole('dialog').waitFor();await page.getByRole('dialog').getByRole('button',{name:'Cancel',exact:true}).click();await page.setViewportSize({width:390,height:844});await page.evaluate(()=>window.scrollTo(0,0));await page.screenshot({path:'test-results/setup-mobile.png',fullPage:true});assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));assert.deeLine truncated
+1
View File
@@ -61,6 +61,7 @@ pub async fn account(db: &Store, config: &Config, name: &str, groups: &[String])
password: "test-only-password".into(),
email: None,
groups: groups.to_vec(),
country: None,
},
)
.await