mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const { DatabaseSync } = require('node:sqlite');
|
||||
const { test } = require('node:test');
|
||||
|
||||
const {
|
||||
createLocalPluginPackageManagementService,
|
||||
} = require('@qinglong/local-admin/package-management');
|
||||
const {
|
||||
LocalSqliteOperationAuthority,
|
||||
} = require('@qinglong/local-sqlite/operation-authority');
|
||||
const {
|
||||
LocalSqlitePluginPackageInstallRepository,
|
||||
} = require('@qinglong/local-sqlite/plugin-package-install');
|
||||
const {
|
||||
migrateLocalSqliteDatabase,
|
||||
} = require('@qinglong/local-sqlite/migration');
|
||||
const {
|
||||
PLUGIN_PACKAGE_API_VERSION,
|
||||
PLUGIN_PACKAGE_KIND,
|
||||
planPluginPackageInstall,
|
||||
} = require('@qinglong/runtime-core/plugin-package');
|
||||
|
||||
const OWNER = Object.freeze({ type: 'user', id: 'usr_owner' });
|
||||
const CONSUMER = Object.freeze({
|
||||
subject: { type: 'system', id: 'local_package_dispatcher' },
|
||||
authenticationId: 'local-package-dispatcher-auth',
|
||||
});
|
||||
|
||||
function actionInput() {
|
||||
const manifest = {
|
||||
apiVersion: PLUGIN_PACKAGE_API_VERSION,
|
||||
kind: PLUGIN_PACKAGE_KIND,
|
||||
metadata: {
|
||||
name: 'example-monitor',
|
||||
displayName: 'Example Monitor',
|
||||
version: '1.2.0',
|
||||
description: 'One bounded package',
|
||||
license: 'Apache-2.0',
|
||||
},
|
||||
spec: {
|
||||
compatibility: {
|
||||
qinglong: '>=3.0.0-0 <4.0.0',
|
||||
architectures: ['arm64'],
|
||||
deploymentProfiles: ['edge'],
|
||||
},
|
||||
runtimes: [],
|
||||
resources: {
|
||||
memory: { recommended: '16Mi' },
|
||||
disk: { install: '4Mi', working: '16Mi' },
|
||||
},
|
||||
permissions: {
|
||||
network: { allowedHosts: [] },
|
||||
secrets: [],
|
||||
tools: [],
|
||||
},
|
||||
contents: { tasks: [], workflows: [], prompts: [], tools: [] },
|
||||
},
|
||||
};
|
||||
const environment = {
|
||||
qinglongVersion: '3.0.0-alpha.0',
|
||||
architecture: 'arm64',
|
||||
deploymentProfile: 'edge',
|
||||
runtimes: [],
|
||||
availableMemoryBytes: 128 * 1024 * 1024,
|
||||
availableDiskBytes: 256 * 1024 * 1024,
|
||||
};
|
||||
return {
|
||||
lockId: 'proposal-monitor-v1',
|
||||
projectId: 'default',
|
||||
manifest,
|
||||
plan: planPluginPackageInstall(manifest, environment),
|
||||
environment,
|
||||
source: {
|
||||
kind: 'offline',
|
||||
locator: `offline:sha256:${'a'.repeat(64)}`,
|
||||
artifactDigest: 'a'.repeat(64),
|
||||
artifactBytes: 2_048,
|
||||
contentDigest: 'b'.repeat(64),
|
||||
},
|
||||
architecture: 'arm64',
|
||||
deploymentProfile: 'edge',
|
||||
targetGeneration: 1,
|
||||
};
|
||||
}
|
||||
|
||||
function principal(now, assurance = 'single_factor') {
|
||||
return {
|
||||
subject: OWNER,
|
||||
authenticationId: `auth-owner-${assurance}`,
|
||||
authenticatedAtMs: now - 100,
|
||||
expiresAtMs: now + 100_000,
|
||||
assurance,
|
||||
};
|
||||
}
|
||||
|
||||
test('runs authenticated local proposal, self-confirmation, consumption and admission end to end', async (t) => {
|
||||
const client = new DatabaseSync(':memory:');
|
||||
t.after(() => client.close());
|
||||
client.exec('PRAGMA foreign_keys = ON');
|
||||
await migrateLocalSqliteDatabase(client);
|
||||
client
|
||||
.prepare(
|
||||
`INSERT INTO "QingLong3ProjectRoleBindings"
|
||||
("project_id","subject_type","subject_id","version","state","role",
|
||||
"mutation_id","changed_by_type","changed_by_id","created_at_ms")
|
||||
VALUES ('default','user','usr_owner',1,'active','owner',
|
||||
'grant-owner-1','user','usr_owner',0)`,
|
||||
)
|
||||
.run();
|
||||
const authority = new LocalSqliteOperationAuthority(client);
|
||||
let now = Date.now();
|
||||
const requestedAtMs = now;
|
||||
let generatedId = 0;
|
||||
const service = createLocalPluginPackageManagementService({
|
||||
authority,
|
||||
profile: 'edge',
|
||||
consumer: CONSUMER,
|
||||
dispatcher: {
|
||||
owner: 'local_package_dispatcher_1',
|
||||
clock: () => now,
|
||||
createId: () => `dispatcher-id-${++generatedId}`,
|
||||
},
|
||||
now: () => now,
|
||||
});
|
||||
const proposalRequest = {
|
||||
actionRef: 'proposal:monitor-v1',
|
||||
approvalRequestId: 'approval-monitor-v1',
|
||||
proposalAuditEventId: '10000000-0000-4000-8000-000000000001',
|
||||
approvalAuditEventId: '10000000-0000-4000-8000-000000000002',
|
||||
requestedAtMs,
|
||||
actionInput: actionInput(),
|
||||
principal: principal(now),
|
||||
};
|
||||
const proposed = await service.propose(proposalRequest);
|
||||
assert.equal(proposed.proposalStatus, 'created');
|
||||
assert.equal(proposed.approvalStatus, 'created');
|
||||
assert.equal(proposed.approvalRequest.decisionMode, 'human_confirmation');
|
||||
const replayed = await service.propose(proposalRequest);
|
||||
assert.equal(replayed.proposalStatus, 'existing');
|
||||
assert.equal(replayed.approvalStatus, 'existing');
|
||||
|
||||
now = Date.now();
|
||||
const decided = await service.decide({
|
||||
approvalRequestId: 'approval-monitor-v1',
|
||||
expectedVersion: 1,
|
||||
decisionId: 'decision-monitor-v1',
|
||||
auditEventId: '10000000-0000-4000-8000-000000000003',
|
||||
decision: 'approved',
|
||||
reasonCode: 'reviewed',
|
||||
decidedAtMs: now,
|
||||
principal: principal(now, 'local_console'),
|
||||
});
|
||||
assert.equal(decided.request.state, 'approved');
|
||||
|
||||
now = Date.now();
|
||||
const consumed = await service.consume({
|
||||
approvalRequestId: 'approval-monitor-v1',
|
||||
expectedVersion: 2,
|
||||
consumptionId: 'consume-monitor-v1',
|
||||
dispatchId: 'dispatch-monitor-v1',
|
||||
auditEventId: '10000000-0000-4000-8000-000000000004',
|
||||
consumedAtMs: now,
|
||||
});
|
||||
assert.equal(consumed.request.state, 'consumed');
|
||||
|
||||
now = Date.now();
|
||||
const dispatched = await service.dispatch();
|
||||
assert.equal(dispatched.scanned, 1);
|
||||
assert.equal(dispatched.started, 1);
|
||||
assert.equal(dispatched.succeeded, 1);
|
||||
const installation = await new LocalSqlitePluginPackageInstallRepository(
|
||||
authority,
|
||||
).find('default', 'example-monitor');
|
||||
assert.equal(installation.state, 'queued');
|
||||
assert.equal(installation.targetGeneration, 1);
|
||||
|
||||
const inspected = await service.inspect(
|
||||
'proposal:monitor-v1',
|
||||
'approval-monitor-v1',
|
||||
);
|
||||
assert.equal(inspected.proposal.actionRef, 'proposal:monitor-v1');
|
||||
assert.equal(inspected.approvalRequest.state, 'consumed');
|
||||
assert.equal(
|
||||
client
|
||||
.prepare(
|
||||
`SELECT count(*) AS count
|
||||
FROM "QingLong3SecurityAuditEvents"
|
||||
WHERE "operation_id" IN (
|
||||
'plugin_package.propose',
|
||||
'approval.request',
|
||||
'approval.decide',
|
||||
'approval.consume',
|
||||
'plugin_package.admit'
|
||||
)`,
|
||||
)
|
||||
.get().count,
|
||||
5,
|
||||
);
|
||||
assert.equal(
|
||||
require('..').createLocalPluginPackageManagementService,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects weak decisions and expired management commands before mutation', async (t) => {
|
||||
const client = new DatabaseSync(':memory:');
|
||||
t.after(() => client.close());
|
||||
client.exec('PRAGMA foreign_keys = ON');
|
||||
await migrateLocalSqliteDatabase(client);
|
||||
client
|
||||
.prepare(
|
||||
`INSERT INTO "QingLong3ProjectRoleBindings"
|
||||
("project_id","subject_type","subject_id","version","state","role",
|
||||
"mutation_id","changed_by_type","changed_by_id","created_at_ms")
|
||||
VALUES ('default','user','usr_owner',1,'active','owner',
|
||||
'grant-owner-1','user','usr_owner',0)`,
|
||||
)
|
||||
.run();
|
||||
const authority = new LocalSqliteOperationAuthority(client);
|
||||
let now = Date.now();
|
||||
const requestedAtMs = now;
|
||||
const service = createLocalPluginPackageManagementService({
|
||||
authority,
|
||||
profile: 'edge',
|
||||
consumer: CONSUMER,
|
||||
dispatcher: {
|
||||
owner: 'local_package_dispatcher_2',
|
||||
clock: () => now,
|
||||
createId: () => 'dispatcher-id-fixed',
|
||||
},
|
||||
approvalLifetimeMs: 1_000,
|
||||
now: () => now,
|
||||
});
|
||||
await service.propose({
|
||||
actionRef: 'proposal:monitor-weak-v1',
|
||||
approvalRequestId: 'approval-monitor-weak-v1',
|
||||
proposalAuditEventId: '20000000-0000-4000-8000-000000000001',
|
||||
approvalAuditEventId: '20000000-0000-4000-8000-000000000002',
|
||||
requestedAtMs,
|
||||
actionInput: actionInput(),
|
||||
principal: principal(now),
|
||||
});
|
||||
now = requestedAtMs + 100;
|
||||
await assert.rejects(
|
||||
service.decide({
|
||||
approvalRequestId: 'approval-monitor-weak-v1',
|
||||
expectedVersion: 1,
|
||||
decisionId: 'decision-monitor-weak-v1',
|
||||
auditEventId: '20000000-0000-4000-8000-000000000003',
|
||||
decision: 'approved',
|
||||
reasonCode: 'reviewed',
|
||||
decidedAtMs: now,
|
||||
principal: principal(now),
|
||||
}),
|
||||
{ code: 'APPROVAL_HUMAN_DECISION_REQUIRED' },
|
||||
);
|
||||
now = requestedAtMs + 1_000;
|
||||
await assert.rejects(
|
||||
service.propose({
|
||||
actionRef: 'proposal:expired-v1',
|
||||
approvalRequestId: 'approval-expired-v1',
|
||||
proposalAuditEventId: '20000000-0000-4000-8000-000000000004',
|
||||
approvalAuditEventId: '20000000-0000-4000-8000-000000000005',
|
||||
requestedAtMs,
|
||||
actionInput: actionInput(),
|
||||
principal: principal(now),
|
||||
}),
|
||||
{ code: 'PLUGIN_PACKAGE_MANAGEMENT_REQUEST_INVALID' },
|
||||
);
|
||||
assert.equal(
|
||||
client
|
||||
.prepare(
|
||||
`SELECT count(*) AS count
|
||||
FROM "QingLong3SecurityAuditEvents"
|
||||
WHERE "operation_id" = 'approval.decide'`,
|
||||
)
|
||||
.get().count,
|
||||
0,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user