QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow d20a578c7d818bc7ca32a2ac2c675da4e4f27385] (pull_request) Successful in 5s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 4s
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 5s
maven / build (push) Failing after 42s
build / maven (push) Failing after 42s
One reviewable integration on current develop 7d61f29e. It starts from
feat/record-files-standalone (attachment lifecycle, upload journal, retention
SPI; descends from develop) and adds the owner stack content from
feature/collab, feature/collab-int-20261029, feature/field-values-1196,
feature/presence-recovery-1300, test/presence-capacity-1302 and the
fix/attachment-transactions-1289 / fix/mywork-query-bounds-1291 work, plus
KOF22 adaptations: CollaborationQBitConfig.attachmentLifecycle,
CollaborationRecords.insertActivity/query(transaction), ActivityWriter source
feature routing, and the Redis/fixture tests.
Build: develop CI files are preserved. pom.xml only adds the Redis API
guardrail allow-list and an optional qbit-redis dependency; the core and
qbit-redis versions stay on the canonical SNAPSHOT lines with markers to repin
after the core and redis integration PRs are published (order CORE -> REDIS ->
COLLAB). No PRIVATE or feature-snapshot coordinate is introduced. Not yet
compiled: it needs the core integration artifact (virtual-field providers).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
61 lines
2.9 KiB
JavaScript
61 lines
2.9 KiB
JavaScript
// Verify owning-node routing with real published UI and backend artifacts.
|
|
import { createRequire } from 'node:module';
|
|
import { resolve } from 'node:path';
|
|
import { writeFile } from 'node:fs/promises';
|
|
|
|
const [frontendRepo, base, output] = process.argv.slice(2);
|
|
if (!frontendRepo || !output || !['localhost', '127.0.0.1'].includes(new URL(base).hostname)) {
|
|
throw new Error('Expected frontend repo, owned loopback URL and result path');
|
|
}
|
|
const require = createRequire(resolve(frontendRepo, 'package.json'));
|
|
const { chromium, expect } = require('@playwright/test');
|
|
const result = { status: 'FAIL', checks: [], errors: [] };
|
|
const browser = await chromium.launch({ headless: true });
|
|
try {
|
|
const context = await browser.newContext();
|
|
await context.addCookies([
|
|
{ name: 'collab_dev_user', value: 'alice', url: base },
|
|
{ name: 'presence_node', value: 'first', url: base },
|
|
]);
|
|
const page = await context.newPage();
|
|
page.on('pageerror', error => result.errors.push(error.message));
|
|
const subscribed = page.waitForResponse(response =>
|
|
new URL(response.url()).pathname.endsWith('/realtime/subscriptions') && response.status() === 200);
|
|
await page.goto(`${base}/app/person/1/edit?createMockSession=true`);
|
|
await expect(page.locator('[data-qqq-id="firstName"]')).toBeVisible();
|
|
await page.locator('[data-qqq-id="firstName"]').fill('Keep this unsaved draft');
|
|
const response = await subscribed;
|
|
expect(response.headers()['x-acceptance-node']).toBe('first');
|
|
const { connectionId } = response.request().postDataJSON();
|
|
expect(typeof connectionId).toBe('string');
|
|
for (const [node, expected] of [['first', 200], ['second', 404], ['first', 200]]) {
|
|
// Keep the same session/user cookies and change only the test routing cookie.
|
|
const cookies = (await context.cookies(base)).map(cookie =>
|
|
`${cookie.name}=${cookie.name === 'presence_node' ? node : cookie.value}`).join('; ');
|
|
const changed = await context.request.post(`${base}/qqq/v1/realtime/subscriptions`, {
|
|
headers: { Cookie: cookies },
|
|
data: { connectionId, add: ['record:person:1'], remove: [] }, timeout: 10000,
|
|
});
|
|
try {
|
|
result.checks.push({ node: changed.headers()['x-acceptance-node'], status: changed.status() });
|
|
expect(changed.headers()['x-acceptance-node']).toBe(node);
|
|
expect(changed.status()).toBe(expected);
|
|
if (expected === 200) {
|
|
const body = await changed.json();
|
|
expect(body.active).toContain('record:person:1');
|
|
expect(body.refused ?? []).toEqual([]);
|
|
}
|
|
} finally { await changed.dispose(); }
|
|
}
|
|
await expect(page.locator('[data-qqq-id="firstName"]')).toHaveValue('Keep this unsaved draft');
|
|
expect(result.errors).toEqual([]);
|
|
result.status = 'PASS';
|
|
} catch (error) {
|
|
result.failure = String(error);
|
|
process.exitCode = 1;
|
|
} finally {
|
|
await browser.close();
|
|
await writeFile(output, JSON.stringify(result, null, 2));
|
|
console.log(JSON.stringify(result, null, 2));
|
|
}
|