conductor/scripts/test-social-identity.mjs

79 lines
5.6 KiB
JavaScript

// Test an installed Conductor build with its real bridge and a read-only scheduler fixture.
import {chromium,expect} from '@playwright/test';
import {spawn,spawnSync} from 'node:child_process';
import fs from 'node:fs/promises';
import path from 'node:path';
import http from 'node:http';
import net from 'node:net';
import {tmpdir} from 'node:os';
const build=path.resolve(process.argv[2]||'.local/catalog-build-verification/.local/conductor-build');
const data=await fs.mkdtemp(path.join(tmpdir(),'scheduler-catalog-'));
const artifact=path.resolve('test-results/slice10-social-integration');await fs.mkdir(artifact,{recursive:true});
const fixture=http.createServer((req,res)=>{
let value;
if(req.method==='GET'&&req.url==='/social-scheduler/session')value={displayName:'Customer administrator',role:req.headers['x-conductor-role'],workspaces:[{id:'customer',name:'Customer workspace'}],preview:false,publishingEnabled:false};
else if(req.method==='GET'&&req.url==='/social-scheduler/workspaces/customer')value={accounts:[],posts:[],publishingEnabled:false};
else {res.writeHead(404);res.end();return;}
res.writeHead(200,{'content-type':'application/json'});res.end(JSON.stringify(value));
});
await new Promise(resolve=>fixture.listen(0,'127.0.0.1',resolve));
const port=await new Promise(resolve=>{const probe=net.createServer();probe.listen(0,'127.0.0.1',()=>{const port=probe.address().port;probe.close(()=>resolve(port));});});
const base='http://127.0.0.1:'+port;
const env={...process.env,PORT:String(port),CONDUCTOR_DATA_DIR:data,CONDUCTOR_STATIC_DIR:path.join(build,'frontend/build'),CONDUCTOR_SECRET_KEY:Buffer.alloc(32,21).toString('base64'),SOCIAL_API_ORIGIN:'http://127.0.0.1:'+fixture.address().port+'/social-scheduler',SOCIAL_INTERNAL_TOKEN:'local-test-fixture',SOCIAL_WORKSPACE_ID:'customer'};
const server=spawn(process.execPath,['backend/dist/index.js'],{cwd:build,env,stdio:['ignore','pipe','pipe']});
let output='';server.stdout.on('data',c=>{output+=c;});server.stderr.on('data',c=>{output+=c;});
let browser,page;
try {
for(let n=0;n<100;n++){
if(server.exitCode!==null)throw new Error(output);
if(await fetch(base+'/api/health').then(r=>r.ok).catch(()=>false))break;
if(n===99)throw new Error('Conductor did not start.');
await new Promise(resolve=>setTimeout(resolve,50));
}
browser=await chromium.launch({headless:true});
const context=await browser.newContext({baseURL:base,viewport:{width:1360,height:980}});
const setup=await context.request.get('/api/auth/setup');
const response=await context.request.post('/api/auth/setup',{headers:{'X-Setup-Token':(await setup.json()).setupToken},data:{username:'catalog-test-admin',password:'catalog-test-password',displayName:'Customer Administrator'}});
expect(response.status()).toBe(201);
page=await context.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message));
await page.goto('/');
await expect(page).toHaveTitle('Conductor');
await page.getByRole('navigation').getByRole('button',{name:'Publishing',exact:true}).click();
await expect(page).toHaveTitle('Publishing · Conductor');
await expect(page.getByText('No included applications are installed yet.',{exact:false})).toBeVisible();
await expect(page.getByRole('link',{name:'Open Social Scheduler',exact:true})).toHaveCount(0);
const manifest=await fs.readFile(path.join(path.resolve(process.argv[3]||'../social-scheduler'),'integration/included-app.json'),'utf8');
for(let n=0;n<2;n++){
const result=spawnSync(process.execPath,['backend/dist/scripts/includedApps.js','register'],{cwd:build,env,input:manifest,encoding:'utf8'});
expect(result.status,result.stderr).toBe(0);
}
await page.getByRole('button',{name:'Refresh applications',exact:true}).click();
await expect(page.getByRole('heading',{name:'Social Scheduler',exact:true})).toHaveCount(1);
await expect(page.getByRole('link',{name:'Open Social Scheduler',exact:true})).toHaveAttribute('href','/social-scheduler');
await page.screenshot({path:path.join(artifact,'publishing-social-scheduler.png'),fullPage:true});
await page.getByRole('link',{name:'Configure',exact:true}).click();
await expect(page).toHaveURL(base+'/social-scheduler?view=accounts');
await expect(page.getByRole('heading',{name:'Connect social media'})).toBeVisible();
await expect(page.getByLabel('Bluesky handle')).toBeVisible();
await expect(page).toHaveTitle('Social Scheduler · Conductor');
await expect(page.locator('#conductor-favicon')).toHaveAttribute('href','/favicon.svg');
await page.screenshot({path:path.join(artifact,'configure-social-scheduler.png'),fullPage:true});
await page.goto('/');
await page.getByRole('link',{name:'Open Social Scheduler',exact:true}).click();
await expect(page.getByRole('heading',{name:'Compose a post'})).toBeVisible();
await expect(page).toHaveTitle('Social Scheduler · Conductor');
await expect(page.locator('#conductor-favicon')).toHaveAttribute('href','/favicon.svg');
await expect(page.getByRole('button',{name:'Queue post now'})).toBeDisabled();
await page.goto('/');await expect(page).toHaveTitle('Conductor');
expect(errors).toEqual([]);
console.log('PASS: installed Conductor build, empty-before-registration, real manifest upsert, Publishing card, Configure/Open through authenticated bridge, inherited app title/default icon, reset on return home.');
} catch(error){
if(page)await page.screenshot({path:path.join(artifact,'failure.png'),fullPage:true}).catch(()=>{});
throw error;
} finally {
await browser?.close();
if(server.exitCode===null)await new Promise(resolve=>{server.once('exit',resolve);server.kill('SIGTERM');});
fixture.closeAllConnections();await new Promise(resolve=>fixture.close(resolve));
await fs.rm(data,{recursive:true,force:true});
}