补充BgFilter全进程与真实链路冒烟验证
新增32/40/48并发、五类故障与harness自测。 新增真实OSS和BgFilter契约冒烟并严格清理临时对象。 补充本地运行方式、凭据边界与staging验收范围。
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,190 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import http from 'node:http';
|
||||
import { afterEach, describe, test } from 'node:test';
|
||||
|
||||
import {
|
||||
buildIsolatedWorkerEnv,
|
||||
createProviderGate,
|
||||
createProviderSequenceBehavior,
|
||||
SMOKE_PNG_BYTES,
|
||||
startMockBgfilterProvider,
|
||||
} from './bgfilter-worker-load-smoke.mjs';
|
||||
|
||||
const providers = [];
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(providers.splice(0).map((provider) => provider.close()));
|
||||
});
|
||||
|
||||
describe('bgfilter worker smoke harness', () => {
|
||||
test('worker 环境不继承真实服务密钥并固定使用假 OSS 配置', () => {
|
||||
const env = buildIsolatedWorkerEnv({
|
||||
processEnv: {
|
||||
ALIYUN_OSS_ACCESS_KEY_SECRET: 'real-oss-secret',
|
||||
GENARRATIVE_BGFILTER_INTERNAL_TOKEN: 'real-internal-token',
|
||||
GENARRATIVE_EDITOR_BGFILTER_TOKEN: 'real-provider-token',
|
||||
PATH: '/safe/bin',
|
||||
VECTOR_ENGINE_API_KEY: 'real-vector-secret',
|
||||
},
|
||||
providerBaseUrl: 'http://127.0.0.1:19001',
|
||||
tempRoot: '/tmp/bgfilter-load-smoke-test',
|
||||
token: 'ephemeral-test-token',
|
||||
workerPort: 19002,
|
||||
});
|
||||
|
||||
assert.equal(env.PATH, '/safe/bin');
|
||||
assert.equal(
|
||||
env.GENARRATIVE_BGFILTER_INTERNAL_TOKEN,
|
||||
'ephemeral-test-token',
|
||||
);
|
||||
assert.equal(env.ALIYUN_OSS_ENDPOINT, 'oss-cn-shanghai.invalid');
|
||||
assert.notEqual(env.ALIYUN_OSS_ACCESS_KEY_SECRET, 'real-oss-secret');
|
||||
assert.equal(env.GENARRATIVE_EDITOR_BGFILTER_TOKEN, undefined);
|
||||
assert.equal(env.VECTOR_ENGINE_API_KEY, undefined);
|
||||
assert.ok(!Object.values(env).includes('real-internal-token'));
|
||||
assert.ok(!Object.values(env).includes('real-provider-token'));
|
||||
assert.ok(!Object.values(env).includes('real-vector-secret'));
|
||||
});
|
||||
|
||||
test('loopback mock 完整读取 multipart 后记录并发并返回合法 PNG 字节', async () => {
|
||||
const provider = await startMockBgfilterProvider({ delayMs: 25 });
|
||||
providers.push(provider);
|
||||
const request = multipartFixture();
|
||||
|
||||
const responses = await Promise.all([
|
||||
postMultipart(provider.baseUrl, request),
|
||||
postMultipart(provider.baseUrl, request),
|
||||
]);
|
||||
|
||||
for (const response of responses) {
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.equal(response.contentType, 'image/png');
|
||||
assert.ok(response.body.equals(SMOKE_PNG_BYTES));
|
||||
}
|
||||
const stats = provider.snapshot();
|
||||
assert.equal(stats.active, 0);
|
||||
assert.equal(stats.peak, 2);
|
||||
assert.equal(stats.requests, 2);
|
||||
assert.deepEqual(stats.violations, []);
|
||||
assert.equal(stats.timeline.filter((event) => event.event === 'start').length, 2);
|
||||
assert.equal(stats.timeline.filter((event) => event.event === 'finish').length, 2);
|
||||
});
|
||||
|
||||
test('provider gate 与 sequence behavior 生成无重叠 timeline', async () => {
|
||||
const gate = createProviderGate();
|
||||
const provider = await startMockBgfilterProvider({
|
||||
behavior: createProviderSequenceBehavior([503, 200]),
|
||||
delayMs: 5,
|
||||
gate,
|
||||
});
|
||||
providers.push(provider);
|
||||
const request = multipartFixture();
|
||||
let firstSettled = false;
|
||||
const first = postMultipart(provider.baseUrl, request).finally(() => {
|
||||
firstSettled = true;
|
||||
});
|
||||
|
||||
await provider.waitFor((stats) => stats.active === 1, { timeoutMs: 1_000 });
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
assert.equal(firstSettled, false);
|
||||
gate.release();
|
||||
assert.equal((await first).statusCode, 503);
|
||||
assert.equal((await postMultipart(provider.baseUrl, request)).statusCode, 200);
|
||||
|
||||
const stats = provider.snapshot();
|
||||
assert.equal(stats.peak, 1);
|
||||
assert.deepEqual(
|
||||
stats.timeline.map((event) => [
|
||||
event.attempt,
|
||||
event.event,
|
||||
event.statusCode ?? null,
|
||||
]),
|
||||
[
|
||||
[1, 'start', null],
|
||||
[1, 'finish', 503],
|
||||
[2, 'start', null],
|
||||
[2, 'finish', 200],
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test('provider 可在成功响应 body 中途 reset 并记录完成类型', async () => {
|
||||
const provider = await startMockBgfilterProvider({
|
||||
behavior: createProviderSequenceBehavior([
|
||||
{ resetMidBody: true, statusCode: 200 },
|
||||
]),
|
||||
delayMs: 5,
|
||||
});
|
||||
providers.push(provider);
|
||||
|
||||
await assert.rejects(postMultipart(provider.baseUrl, multipartFixture()));
|
||||
|
||||
const stats = provider.snapshot();
|
||||
assert.equal(stats.active, 0);
|
||||
assert.equal(stats.peak, 1);
|
||||
assert.equal(stats.requests, 1);
|
||||
assert.deepEqual(stats.violations, []);
|
||||
assert.equal(stats.timeline[1]?.completion, 'mid_body_reset');
|
||||
});
|
||||
});
|
||||
|
||||
function multipartFixture() {
|
||||
const boundary = 'bgfilter-load-smoke-boundary';
|
||||
const body = Buffer.from(
|
||||
[
|
||||
`--${boundary}`,
|
||||
'Content-Disposition: form-data; name="image_url"',
|
||||
'',
|
||||
'https://example.invalid/source.png',
|
||||
`--${boundary}`,
|
||||
'Content-Disposition: form-data; name="seg_model"',
|
||||
'',
|
||||
'birefnet',
|
||||
`--${boundary}`,
|
||||
'Content-Disposition: form-data; name="background_mode"',
|
||||
'',
|
||||
'complex',
|
||||
`--${boundary}`,
|
||||
'Content-Disposition: form-data; name="cross_check"',
|
||||
'',
|
||||
'off',
|
||||
`--${boundary}--`,
|
||||
'',
|
||||
].join('\r\n'),
|
||||
'utf8',
|
||||
);
|
||||
return { body, boundary };
|
||||
}
|
||||
|
||||
function postMultipart(baseUrl, { body, boundary }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = http.request(
|
||||
`${baseUrl}/remove-background`,
|
||||
{
|
||||
agent: false,
|
||||
headers: {
|
||||
'Content-Length': String(body.length),
|
||||
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
||||
},
|
||||
method: 'POST',
|
||||
},
|
||||
(response) => {
|
||||
const chunks = [];
|
||||
response.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
||||
response.once('aborted', () => {
|
||||
reject(new Error('mock provider 响应在 body 中途中止'));
|
||||
});
|
||||
response.once('error', reject);
|
||||
response.once('end', () => {
|
||||
resolve({
|
||||
body: Buffer.concat(chunks),
|
||||
contentType: String(response.headers['content-type'] ?? ''),
|
||||
statusCode: response.statusCode ?? 0,
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
request.once('error', reject);
|
||||
request.end(body);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user