Fix bootstrap image preservation and ingest websocket close recursion
All checks were successful
deploy / deploy (push) Successful in 32s
All checks were successful
deploy / deploy (push) Successful in 32s
Proof: npm test; npm run operator-dashboard:build; node --test test/near-intents-ws.test.mjs; bash -n scripts/deploy/bootstrap.sh; PYTHONPATH=. python3 test/bootstrap_script_static_test.py; PYTHONPATH=. python3 test/render_release_manifest_test.py; PYTHONPATH=. python3 test/repo_deployments_test.py; PYTHONPATH=. python3 test/ntfy_manifest_test.py; kubectl kustomize deploy/k8s/base. Assumptions: bootstrap should preserve an existing deployed release image when refreshing app secrets; websocket close/error events may be reentrant in the Node runtime. Still fake: Notification emissions are limited to credited deposits, completed withdrawals, and completed trades with durable inventory movement; generic alert notification policy remains disabled.
This commit is contained in:
parent
c5a214ce06
commit
ea0a7cbb4c
4 changed files with 75 additions and 2 deletions
|
|
@ -196,8 +196,22 @@ kubectl -n "$PROJECT_NAMESPACE" create secret docker-registry "$PROJECT_REGISTRY
|
||||||
--docker-password="$REGISTRY_PASSWORD" \
|
--docker-password="$REGISTRY_PASSWORD" \
|
||||||
--dry-run=client -o yaml | kubectl apply -f -
|
--dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
|
||||||
|
current_release_image() {
|
||||||
|
kubectl -n "$PROJECT_NAMESPACE" get deploy operator-dashboard \
|
||||||
|
-o jsonpath='{.spec.template.spec.containers[0].image}' 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_MANIFEST_IMAGE="${PROJECT_RELEASE_IMAGE:-$(current_release_image)}"
|
||||||
|
BOOTSTRAP_IMAGE="ghcr.io/example/unrip:bootstrap"
|
||||||
|
|
||||||
echo "applying app manifests"
|
echo "applying app manifests"
|
||||||
|
if [[ -n "$APP_MANIFEST_IMAGE" && "$APP_MANIFEST_IMAGE" != "$BOOTSTRAP_IMAGE" ]]; then
|
||||||
|
kubectl kustomize "$ROOT_DIR/deploy/k8s/base" \
|
||||||
|
| python3 "$ROOT_DIR/scripts/deploy/render_release_manifest.py" --image "$APP_MANIFEST_IMAGE" \
|
||||||
|
| kubectl apply -f -
|
||||||
|
else
|
||||||
kubectl apply -k "$ROOT_DIR/deploy/k8s/base"
|
kubectl apply -k "$ROOT_DIR/deploy/k8s/base"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "upserting Forgejo repo settings"
|
echo "upserting Forgejo repo settings"
|
||||||
forgejo_args=()
|
forgejo_args=()
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ export async function startNearIntentsWs({
|
||||||
let lastConnectedAt = null;
|
let lastConnectedAt = null;
|
||||||
let lastDisconnectedAt = null;
|
let lastDisconnectedAt = null;
|
||||||
let lastReconnectAt = null;
|
let lastReconnectAt = null;
|
||||||
|
const closingSockets = new WeakSet();
|
||||||
|
|
||||||
function connect() {
|
function connect() {
|
||||||
if (closed) return;
|
if (closed) return;
|
||||||
|
|
@ -171,7 +172,8 @@ export async function startNearIntentsWs({
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeSocket(ws) {
|
function closeSocket(ws) {
|
||||||
if (!ws || ws.readyState > WebSocket.OPEN) return;
|
if (!ws || ws.readyState > WebSocket.OPEN || closingSockets.has(ws)) return;
|
||||||
|
closingSockets.add(ws);
|
||||||
try {
|
try {
|
||||||
ws.close();
|
ws.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
18
test/bootstrap_script_static_test.py
Normal file
18
test/bootstrap_script_static_test.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import pathlib
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
class BootstrapScriptStaticTest(unittest.TestCase):
|
||||||
|
def test_bootstrap_renders_existing_release_image_instead_of_reapplying_placeholders(self):
|
||||||
|
source = (ROOT / 'scripts/deploy/bootstrap.sh').read_text()
|
||||||
|
self.assertIn('PROJECT_RELEASE_IMAGE', source)
|
||||||
|
self.assertIn('current_release_image', source)
|
||||||
|
self.assertIn('render_release_manifest.py', source)
|
||||||
|
self.assertIn('ghcr.io/example/unrip:bootstrap', source)
|
||||||
|
self.assertIn('kubectl kustomize "$ROOT_DIR/deploy/k8s/base"', source)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
@ -23,6 +23,8 @@ function installMockWebSocket() {
|
||||||
this.readyState = MockWebSocket.CONNECTING;
|
this.readyState = MockWebSocket.CONNECTING;
|
||||||
this.sent = [];
|
this.sent = [];
|
||||||
this.listeners = new Map();
|
this.listeners = new Map();
|
||||||
|
this.closeCalls = 0;
|
||||||
|
this.emitErrorDuringClose = false;
|
||||||
instances.push(this);
|
instances.push(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,7 +39,11 @@ function installMockWebSocket() {
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
|
this.closeCalls += 1;
|
||||||
if (this.readyState === MockWebSocket.CLOSED) return;
|
if (this.readyState === MockWebSocket.CLOSED) return;
|
||||||
|
if (this.emitErrorDuringClose) {
|
||||||
|
this.emit('error', new Error('socket failed while closing'));
|
||||||
|
}
|
||||||
this.readyState = MockWebSocket.CLOSED;
|
this.readyState = MockWebSocket.CLOSED;
|
||||||
this.emit('close', {});
|
this.emit('close', {});
|
||||||
}
|
}
|
||||||
|
|
@ -96,3 +102,36 @@ test('near intents ingest reconnects after websocket error before open without r
|
||||||
mock.restore();
|
mock.restore();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('near intents websocket close is reentrant-safe when close emits an error', async () => {
|
||||||
|
const mock = installMockWebSocket();
|
||||||
|
const producer = { sendJson: async () => {} };
|
||||||
|
const client = await startNearIntentsWs({
|
||||||
|
apiKey: 'api-key',
|
||||||
|
wsUrl: 'wss://relay.example/ws',
|
||||||
|
pairFilter: ['btc', 'eure'],
|
||||||
|
producer,
|
||||||
|
rawTopic: 'raw.near_intents.quote',
|
||||||
|
normalizedTopic: 'norm.swap_demand',
|
||||||
|
reconnectDelayMs: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert.equal(mock.instances.length, 1);
|
||||||
|
mock.instances[0].open();
|
||||||
|
mock.instances[0].emitErrorDuringClose = true;
|
||||||
|
|
||||||
|
assert.doesNotThrow(() => {
|
||||||
|
mock.instances[0].error(new Error('network failed'));
|
||||||
|
});
|
||||||
|
await delay(10);
|
||||||
|
|
||||||
|
assert.equal(mock.instances[0].closeCalls, 1);
|
||||||
|
assert.equal(client.getState().connected, false);
|
||||||
|
assert.ok(client.getState().reconnect_count >= 2);
|
||||||
|
assert.ok(mock.instances.length >= 2);
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
|
mock.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue