Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

simlock alerts test checks a destination

Aperta
#721 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
35/100
Tipo di issue
Funzionalità
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
node.js, typescript
Ambito
api, backend, cli, devtools

Direzione di ricerca

Start with the operation and dispatcher patterns in src/contract/operations.ts, src/gateway/dispatcher.ts, and src/daemon/dispatcher.ts, then trace request handling in src/daemon/server.ts and the admin client and CLI command patterns. Implement the specified alerts.test flow across the listed contract, daemon, client, CLI, and documentation files, including the protocol version bump. Done means the sample is sent once with the stated timeout and failures, worker behavior, CLI output, and tests match the specification.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

task:ready

Part of #707.

Scope

simlock alerts test <destination> sends one sample message to one destination of the gateway, once, and says whether the receiver took it. After this PR, an operator finds a wrong URL or secret before an outage, not during one.

In short

The command asks the gateway to post one sample message to the named destination: one try, 10 s at most, counted from the POST's start, outside the destination's queue. It prints the receiver's HTTP status, or fails with the status or the error. A worker refuses it.

sequenceDiagram
  participant C as simlock alerts test ops-slack
  participant G as Gateway
  participant R as Receiver
  C->>G: alerts.test {destination: "ops-slack"} (admin)
  G->>R: POST sample (one try, 10 s)
  alt 2xx
    R-->>G: 200
    G-->>C: {destination, status: 200}
  else other status, network error or timeout
    G-->>C: ALERT_SEND_FAILED
  end

Session, before and after:

$ simlock alerts test ops-slack
{"error":{"code":"USAGE","message":"Unknown command: alerts (run `simlock --help` for usage)"}}      # before, exit 2

$ simlock alerts test ops-slack
Sent a test message to ops-slack: HTTP 200                                                          # after, exit 0

$ simlock alerts test ops-slack --json
{"destination":"ops-slack","status":200}

$ simlock alerts test ops-slack
{"error":{"code":"ALERT_SEND_FAILED","message":"ops-slack answered HTTP 404"}}                      # exit 1

$ simlock alerts test ops-slack
{"error":{"code":"ALERT_SEND_FAILED","message":"ops-slack could not be reached: ECONNREFUSED"}}     # exit 1

$ simlock alerts test ops-slack
{"error":{"code":"ALERT_SEND_FAILED","message":"ops-slack did not answer within 10 s"}}            # exit 1

$ simlock alerts test nope
{"error":{"code":"UNKNOWN_ALERT_DESTINATION","message":"No alert destination named \"nope\". Check gateway.alerts.destinations in the gateway's config."}}   # exit 12

$ simlock alerts test ops-slack          # against a worker
{"error":{"code":"UNSUPPORTED_IN_WORKER_MODE","message":"alerts.test sends to a gateway's alert destinations, and this daemon is not a gateway; run it against the gateway"}}   # exit 2

$ simlock alerts test
{"error":{"code":"USAGE","message":"Expected destination (run `simlock --help` for usage)"}}         # exit 2

$ simlock alerts            # and simlock alerts --help: usage on stdout, exit 0
Usage: simlock alerts test <destination> [--json]

$ simlock alerts foo
{"error":{"code":"USAGE","message":"Unknown alerts command: foo (run `simlock --help` for usage)"}}  # exit 2

The sample message:

  • generic: the envelope {"id": "evt_<new id>", "seq": 0, "timestamp": <now, ms>, "event": "alerts.test-sent", "payload": {"destination": "<name>"}, "module": "alerts"}, with the headers and signature of every generic message. It is not put on the bus and is not in docs/EVENTS.md; docs/CONFIGURATION.md tells receivers it comes only from simlock alerts test. X-Simlock-Event is alerts.test-sent.
  • slack: {"text": "🔔 Test message from the simlock gateway to destination <name>."}.

If this goes wrong, the test says "sent" when the receiver refused it, or a working destination looks broken.

Technical spec

Facts

Commit ccb50353.

# Fact Source
F1 An operation is defineOperation({name, role, effect, input, output}) and listed in OPERATIONS; operations.test.ts has a row per operation with its role. src/contract/operations.ts:736-743, :1020-1055; src/contract/operations.test.ts:18-32
F2 The worker's Dispatcher answers gateway-only operations with unsupportedOnWorker(name, message), code UNSUPPORTED_IN_WORKER_MODE, details {operation}. src/daemon/dispatcher.ts:301-307, :1066-1073
F3 GatewayDispatcher is built in startGatewayDaemon from options main.ts passes. src/daemon/main.ts:844-866
F4 An error code is a row {code, kind, cliExitCode, httpStatus} in ERROR_TABLE with a details type in ErrorDetailsMap. UNKNOWN_WORKER is domain, exit 12, 404; WORKER_UNREACHABLE is transport, exit 1, 503. src/contract/errors.ts:20-114, :370-375
F5 The CLI prints a failure as {"error":{"code":…,"message":…}} on stderr and exits with the code's cliExitCode. src/cli/errors.ts:24; node dist/cli/main.js alerts test x printed {"error":{"code":"USAGE","message":"Unknown command: alerts (run \simlock --help` for usage)"}}`, exit 2
F6 A command group is a lazy CommandNode with children in COMMANDS; worker.ts shows subcommandOrUsage, connectDaemonClient, writeResult. src/cli/tree.ts:48-100; src/cli/commands/worker.ts:1-40
F7 The admin client lists each admin call (drainWorker → #call("worker.drain", input)), with input and output types in types.ts, and a rejecting stub for each. src/simlock-client/client.ts:254-259, :446, :671; src/simlock-client/types.ts:77-78
F8 startDaemon has an idGenerator option, default CryptoIdGenerator; event ids match ^evt_[A-Za-z0-9_-]{1,64}$. src/daemon/main.ts:131, :151; src/contract/schemas.ts:775
F9 docs/CLI.md has the exit-code table and one section per command group, e.g. ## simlock worker <list|drain|undrain|remove>; docs/CLIENT.md lists admin calls. docs/CLI.md:75-163, :1119; docs/CLIENT.md:853
F12 The socket serves an operation only if DaemonServer#handleRequest has a case for it; any other name answers UNKNOWN_REQUEST. gateway.dispatch(...) in tests skips that switch. src/daemon/server.ts:1136-1137, :1175-1176, :401
F13 A missing positional goes through requiredPositional: Expected <label> (run \simlock --help` for usage), exit 2; a group with no or --helpsubcommand prints its usage to stdout, exit 0; an unknown one isUnknown command: ` with the hint. src/cli/args.ts:24-27, :49-61; src/cli/commands/worker.ts:20-33; node dist/cli/main.js worker drain → {"error":{"code":"USAGE","message":"Expected worker-id (run \simlock --help` for usage)"}}`
F14 Admin input and output types are re-exported from src/admin/index.ts; operations.test.ts has a role table and an effect matrix. src/admin/index.ts:74-75; src/contract/operations.test.ts:18-32, :175-180
F15 Gateway dispose runs before the server closes sockets; DAEMON_STOPPING gates only new calls. src/daemon/server.ts:406-409, :605-627
F16 GatewayDispatcher declares the shapes it needs from other modules itself (today e.g. GatewayTokenStore, which #712 may remove; the pattern stays); dependency-cruiser counts type-only imports. src/gateway/dispatcher.ts:84-92; .dependency-cruiser.mjs:263
F10 Every contract shape change bumps the protocol by one. src/contract/AGENTS.md:59
F11 #719's createAlerts, WebhookPoster, generic request; #720's slack request. #719, #720
Modules touched

Searches: "worker.drain" (operation, dispatchers, client, HTTP), UNKNOWN_WORKER (errors, CLI docs), COMMANDS.

  • src/contract/operations.ts: alertsTest (alerts.test, admin, write, input {destination: string, 1–32 chars}, output {destination: string, status: number}).
  • src/contract/errors.ts: UNKNOWN_ALERT_DESTINATION {destination}; ALERT_SEND_FAILED {destination, status?: number, error?: string}: status for a non-2xx answer; error timeout, a network code such as ECONNREFUSED, or network-error. src/contract/protocol.ts: bump by one.
  • src/alerts/: Alerts.test(name) as under Surface; createAlerts options gain idGenerator: IdGenerator (for the sample's id); stop() also aborts a test try in flight. A test writes no log line of its own (the dispatch operation line records the call), and a test aborted by stop() is not counted in "Alerts discarded at stop". test() after stop() resolves stopped and posts nothing.
  • src/gateway/dispatcher.ts: alerts.test handler calling a testDestination(name) option (ADR 0035 §1), with the result shape declared in the gateway (F16), not imported from src/alerts; without the option (no destinations) → UNKNOWN_ALERT_DESTINATION.
  • src/daemon/dispatcher.ts: "alerts.test": unsupportedOnWorker("alerts.test", "alerts.test sends to a gateway's alert destinations, and this daemon is not a gateway; run it against the gateway").
  • src/daemon/server.ts: case "alerts.test" in #handleRequest (F12).
  • src/daemon/main.ts: passes testDestination and idGenerator through.
  • src/simlock-client/: testAlertDestination(input), its types and stub; src/admin/index.ts re-exports the types (F14).
  • src/contract/operations.test.ts: role table and effect matrix rows. PROTOCOL_VERSION_RANGE and every test that pins the current number (search the number itself and "protocol " in src/, e.g. src/contract/protocol.test.ts, src/daemon/server.test.ts, src/daemon-protocol/index.test.ts, src/gateway/service.test.ts).
  • src/cli/tree.ts, src/cli/commands/alerts.ts (new), and the root usage in src/cli/index.ts:14-35 gains alerts test <destination>.
  • Docs: docs/CLI.md (a section, two exit-code rows), docs/CLIENT.md (the call), docs/CONFIGURATION.md (point at the command), src/alerts/AGENTS.md.
flowchart LR
  CLI["cli/commands/alerts.ts (new)"] --> CL["simlock-client *"]
  CL --> CT["contract *"]
  GD["gateway/dispatcher.ts *"] -. "testDestination(name)" .-> AL["alerts/ *"]
  SRV["daemon/server.ts *"] --> GD
  WD["daemon/dispatcher.ts *"]
  main["daemon/main.ts *"] --> GD
  main --> AL
Surface and extension points

Alerts gains test(destination: string): Promise<AlertTestResult>. It never rejects. AlertTestResult is {ok: true, status} or {ok: false, reason: "unknown-destination" | "status" | "unreachable" | "timeout" | "stopped", status?, error?}, error being the network code as in #719. The gateway dispatcher maps it: ok → output; unknown-destination → UNKNOWN_ALERT_DESTINATION; status, unreachable, timeout → ALERT_SEND_FAILED; stopped → DAEMON_STOPPING. No new extension point.

Contract and event changes
  • New operation alerts.test; new error codes UNKNOWN_ALERT_DESTINATION (domain, exit 12, HTTP 404) and ALERT_SEND_FAILED (transport, exit 1, HTTP 502). This PR bumps the protocol by one, min with it; the PR that merges second among the open tasks bumping this line (today #713, #714, #629-#632, #635-#637, #719, #720) bumps again. #713 also adds an operation to OPERATIONS, both dispatchers, server.ts, the client and cli/tree.ts: the second to merge takes the base branch and keeps both entries.
  • Input destination: a string of 1 to 32 characters; any other → BAD_REQUEST. It is matched against configured names only, never used in a URL.
  • Messages: No alert destination named "<destination>". Check gateway.alerts.destinations in the gateway's config.; <destination> answered HTTP <status>; <destination> could not be reached: <network code, e.g. ECONNREFUSED, else network-error> (never the error's message, which can hold the URL); <destination> did not answer within 10 s.
  • No event: the sample is not a fleet fact and is not put on the bus.
  • No HTTP route (ADR 0035 §6).
Other code on the same state

Searches: the destination queue in src/alerts (#719), poster.post callers.

  • The test does not use or wait for the destination's queue. A queued message may be in flight at the same time; the receiver sees both, in either order.
  • The destination config is read-only after start (#719).
Failure modes
Call Outcome What happens Limit, counted from
poster.post (sample) 2xx output {destination, status}; CLI exit 0 —
poster.post other status (3xx not followed) ALERT_SEND_FAILED "answered HTTP "; exit 1 —
poster.post rejects ALERT_SEND_FAILED "could not be reached: <network code, else network-error>", never the error's message; exit 1 —
poster.post no answer abort; ALERT_SEND_FAILED "did not answer within 10 s"; exit 1 10 s from the POST's start
lookup unknown name, or no destinations UNKNOWN_ALERT_DESTINATION; exit 12 —
alerts.test on a worker — UNSUPPORTED_IN_WORKER_MODE; exit 2 —
gateway stops during the test — Alerts.stop() aborts the test try and cancels its timer; test resolves stopped; the call answers DAEMON_STOPPING if its connection is still open (F15) none: stop waits for nothing
  • State: none written. Two tests at once each make their own POST.
Rules in play
  • architecture, bounded cross-process waits: one try, 10 s.
  • safety, "wire input is a claim": destination is only looked up by name.
  • ADR 0012, a worker answers fleet operations as a fleet of one: a gateway-only operation is refused with UNSUPPORTED_IN_WORKER_MODE, as worker.drain is.
  • ADR 0035 §6.
Tests

Seam: the alerts.test operation through GatewayDispatcher with a real createAlerts and FakeWebhookPoster, in src/gateway/dispatcher.test.ts; the CLI in src/cli/index.test.ts; Alerts.test in src/alerts/alerts.test.ts.

  • alerts.test posts one sample to the named destination and answers its status (cases: generic with the sample envelope, headers and a signature that verifies; slack with the sample text). The sample is built by the destination's format definition, given an envelope of event alerts.test-sent; slack's message table gains that one entry.
  • alerts.test fails with ALERT_SEND_FAILED on a non-2xx status, a rejection and no answer within 10 s, and tries once (cases 404, 301, 503, rejection, 10 000 ms)
  • alerts.test answers UNKNOWN_ALERT_DESTINATION for a name not configured and on a gateway with no destinations
  • stop during a test aborts its try, cancels its timer and answers DAEMON_STOPPING (alerts.test.ts for stopped and no live timer; dispatcher.test.ts for the mapping)
  • a refused connection answers could not be reached with the network code, and neither the answer nor the failed call's operation log line holds the URL
  • test after stop resolves stopped and posts nothing
  • the sample is not put on the bus and does not go through the destination's queue (a queued message still in its retry wait is not delayed and not reordered)
  • a worker answers alerts.test with UNSUPPORTED_IN_WORKER_MODE and its message
  • alerts.test needs the admin role and is a write (extends the role table and the effect matrix in operations.test.ts)
  • simlock alerts test prints the sent line, --json prints the output, a missing name prints the Expected line, simlock alerts and --help print the usage, and an unknown subcommand is a usage error (exit 0, 0, 2, 0, 2)
  • simlock alerts test exits 1 on ALERT_SEND_FAILED and 12 on UNKNOWN_ALERT_DESTINATION with the error line
  • the admin client's testAlertDestination sends alerts.test and returns its output (src/simlock-client/client.test.ts, with ScriptedConnection as the existing cases)
  • alerts.test over the gateway's real unix socket reaches a loopback receiver with the signed sample (extends #719's case in gateway-fleet.e2e.test.ts: the admin client connects to the gateway's socket, not gateway.dispatch, so a missing server.ts case fails it; real FetchWebhookPoster)

Done when

  • CI is green with the lines above.
  • In gateway-fleet.e2e.test.ts, alerts.test over the gateway's unix socket with a generic destination on a loopback receiver answers status 200, and the receiver gets the signed sample.
  • docs/CLI.md has the command, its output and both exit-code rows; docs/CLIENT.md has testAlertDestination.

Out of scope

  • An HTTP route or a console button.
  • Testing every destination at once.

Depends on

  • #720

Written by an agent.

Lingua principale
TypeScript
Stelle
20
Fork
2
Merge medio
8h 17m
PR unite (30g)
273

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di callstackincubator/simlock

Tutte le issue di callstackincubator/simlock

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.