Remove initial server health check and bump Harbor versions
This commit is contained in:
@@ -90,6 +90,60 @@ test('state v1 normalizes legacy storage and validates the canonical snapshot',
|
||||
);
|
||||
});
|
||||
|
||||
test('startup discards a rejected cached subscription and returns to first-run', async (t) => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-rejected-cache-'));
|
||||
const port = await freePort();
|
||||
const subscriptionUrl = 'https://provider.example/disabled';
|
||||
const rejectedServer = {
|
||||
type: 'vless',
|
||||
tag: '🚫 Subscription disabled',
|
||||
server: '0.0.0.0',
|
||||
server_port: 1,
|
||||
};
|
||||
const routeRules = [{ type: 'domain_suffix', value: 'example.org', enabled: true }];
|
||||
fs.writeFileSync(path.join(dir, 'state.json'), JSON.stringify({
|
||||
subscriptionUrl,
|
||||
selectedTag: rejectedServer.tag,
|
||||
servers: [rejectedServer],
|
||||
routeRules,
|
||||
}));
|
||||
fs.writeFileSync(path.join(dir, 'subscription-cache.json'), JSON.stringify({
|
||||
url: subscriptionUrl,
|
||||
config: { outbounds: [rejectedServer] },
|
||||
}));
|
||||
fs.writeFileSync(path.join(dir, 'sing-box-config.json'), '{}');
|
||||
|
||||
const child = spawn(process.execPath, ['src/server/index.js'], {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
APP_MODE: 'client',
|
||||
DATA_DIR: dir,
|
||||
PORT: String(port),
|
||||
HARBOR_HOST_NETWORK_STATE: path.join(dir, 'missing-network.json'),
|
||||
},
|
||||
stdio: ['ignore', 'ignore', 'pipe'],
|
||||
});
|
||||
let stderr = '';
|
||||
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
||||
t.after(async () => {
|
||||
child.kill('SIGTERM');
|
||||
if (child.exitCode === null) await new Promise((resolve) => child.once('exit', resolve));
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
const state = await waitForState(port, child, () => stderr);
|
||||
assert.equal(state.subscription.status, 'missing');
|
||||
assert.equal(state.hasSubscription, false);
|
||||
assert.deepEqual(state.servers, []);
|
||||
assert.ok(state.route.localRules.some((rule) => (
|
||||
rule.type === 'domain_suffix' && rule.value === 'example.org' && rule.enabled
|
||||
)));
|
||||
assert.equal(fs.existsSync(path.join(dir, 'subscription-cache.json')), false);
|
||||
assert.equal(fs.existsSync(path.join(dir, 'sing-box-config.json')), false);
|
||||
assert.equal(child.exitCode, null);
|
||||
});
|
||||
|
||||
test('data invariant: API mutations return one snapshot, increase revision and roll back subscription failures', async (t) => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-state-contract-'));
|
||||
const binDir = path.join(dir, 'bin');
|
||||
@@ -121,6 +175,7 @@ setInterval(() => {}, 60_000);
|
||||
let providerFetchCount = 0;
|
||||
let delayedPath = '';
|
||||
let invalidNextPath = '';
|
||||
let trafficExhaustedNextPath = '';
|
||||
let delayedRequestStarted = null;
|
||||
let releaseDelayedRequest = null;
|
||||
const subscriptionServer = http.createServer(async (req, res) => {
|
||||
@@ -134,6 +189,42 @@ setInterval(() => {}, 60_000);
|
||||
res.writeHead(200, { 'content-type': 'text/plain' });
|
||||
return res.end('not a subscription');
|
||||
}
|
||||
if (req.url === '/traffic' || req.url === trafficExhaustedNextPath) {
|
||||
trafficExhaustedNextPath = '';
|
||||
res.writeHead(200, {
|
||||
'content-type': 'application/json',
|
||||
'subscription-userinfo': 'upload=60; download=40; total=100; expire=4102444800',
|
||||
});
|
||||
return res.end(JSON.stringify({
|
||||
outbounds: [{
|
||||
type: 'vless',
|
||||
tag: 'Account unavailable',
|
||||
server: '0.0.0.0',
|
||||
server_port: 1,
|
||||
}],
|
||||
}));
|
||||
}
|
||||
if (req.url === '/expired') {
|
||||
res.writeHead(200, {
|
||||
'content-type': 'application/json',
|
||||
'subscription-userinfo': 'upload=10; download=20; total=100; expire=1',
|
||||
});
|
||||
return res.end(JSON.stringify(config));
|
||||
}
|
||||
if (req.url === '/disabled') {
|
||||
res.writeHead(200, {
|
||||
'content-type': 'application/json',
|
||||
'subscription-userinfo': 'upload=0; download=0; total=100; expire=4102444800',
|
||||
});
|
||||
return res.end(JSON.stringify({
|
||||
outbounds: [{
|
||||
type: 'vless',
|
||||
tag: '🚫 Subscription disabled',
|
||||
server: '0.0.0.0',
|
||||
server_port: 1,
|
||||
}],
|
||||
}));
|
||||
}
|
||||
if (req.url === invalidNextPath) {
|
||||
invalidNextPath = '';
|
||||
res.writeHead(200, { 'content-type': 'text/plain' });
|
||||
@@ -240,6 +331,9 @@ setInterval(() => {}, 60_000);
|
||||
for (const [pathname, expectedCode] of [
|
||||
['/timeout', 'PROVIDER_UNAVAILABLE'],
|
||||
['/invalid', 'SUBSCRIPTION_INVALID'],
|
||||
['/expired', 'SUBSCRIPTION_EXPIRED'],
|
||||
['/traffic', 'SUBSCRIPTION_TRAFFIC_EXHAUSTED'],
|
||||
['/disabled', 'SUBSCRIPTION_DISABLED'],
|
||||
]) {
|
||||
const failedImport = await rawRequest(
|
||||
port,
|
||||
@@ -262,6 +356,16 @@ setInterval(() => {}, 60_000);
|
||||
);
|
||||
}
|
||||
|
||||
trafficExhaustedNextPath = '/subscription/test';
|
||||
const exhaustedRefresh = await rawRequest(port, '/api/subscription/refresh', 'POST');
|
||||
assert.equal(exhaustedRefresh.response.status, 400);
|
||||
assert.equal(exhaustedRefresh.payload.error.code, 'SUBSCRIPTION_TRAFFIC_EXHAUSTED');
|
||||
const stateAfterExhaustedRefresh = await request(port, '/api/state');
|
||||
assert.equal(stateAfterExhaustedRefresh.hasSubscription, true);
|
||||
assert.equal(fs.readFileSync(path.join(dir, 'subscription-cache.json'), 'utf8'), preservedSubscription.cache);
|
||||
assert.equal(fs.readFileSync(path.join(dir, 'sing-box-config.json'), 'utf8'), preservedSubscription.config);
|
||||
revision = stateAfterExhaustedRefresh.revision;
|
||||
|
||||
const missingServer = await rawRequest(
|
||||
port,
|
||||
'/api/apply',
|
||||
|
||||
Reference in New Issue
Block a user