feat(sdk): testing utilities, a real logger, and docs an agent can install from - #32
Merged
Conversation
…stall from
Three things a developer notices in the first ten minutes, none of which
the SDK had.
TESTING. We have hundreds of tests and a customer had none of them. There
was no supported way to write "assert this request would be denied"
against your own rules, so the first time anyone learned what the
middleware does to their traffic was in production -- which is also why
monitor had to become the default mode. createTestHarness() is offline by
default: a WEBDECOY_API_KEY in CI would otherwise turn every unit test
into a live call and file test traffic as real detections in the
customer's dashboard. Each harness gets its own rule state, so rate-limit
counters do not leak between cases and the second test to run does not
fail for reasons belonging to the first. Assertion failures print every
rule and its state, because "expected false to be true" says nothing about
which of six rules was supposed to fire.
LOGGING. It was `debug: boolean` writing to console.log -- either off or
noise, unroutable, unsampleable, unstructured. Now any object with
debug/info/warn/error. Warnings and errors are no longer gated on debug: a
violation that failed to report is not diagnostic output. fromPino()
exists because pino's argument order is reversed, and passing one directly
type-checks and then silently drops every field.
DOCS FOR AGENTS. Coding agents install dependencies now and the repo gave
them nothing to read. llms.txt and AGENTS.md are written for that reader,
and most of AGENTS.md is what NOT to do -- do not enable enforce on a
first install, do not invent an API key, do not leave a proxied app on the
default trustProxy, do not call attackSignatures() a WAF.
Writing them caught a real inconsistency: req.webdecoy is the detection in
the Node adapters while Hono's c.get('webdecoy') was the decision. All
four now also carry webdecoyDecision, which means one thing everywhere.
Removing the `as any` casts that went with it dropped express 12->10 and
nextjs 3->2.
Closes WebDecoy/app#729
Closes WebDecoy/app#734
Closes WebDecoy/app#735
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #729, #734 and #735 — the DX batch.
#734 — testing utilities
The SDK has hundreds of tests and a customer had none of them. There was no supported way to write "assert this request would be denied" against your own rules, so the first time anyone learned what the middleware does to their traffic was in production. That is also why
mode: 'monitor'had to become the default.Three things made that test hard to write by hand, and each is why a helper exists:
WEBDECOY_API_KEYin CI would otherwise turn every unit test into a live network call — slow, flaky, and it files test traffic as real detections in the customer's dashboard.allowNetwork: trueopts in. There's a test assertingfetchis never called.expectDeniedprints every rule and its state. "expected false to be true" tells you nothing about which of six rules was supposed to fire.Shipped as a
/testingsubpath export so none of it reaches production bundles.protectMany()runs a rate limit to its edge without sleeping.#735 — logging
Was
debug: boolean→console.log: either off or noise, unroutable into the app's own logger, unsampleable, unstructured.One deliberate behaviour change: warnings and errors are no longer gated on
debug. A violation that failed to report, or a rejected key, is something the operator needs whether or not they opted into diagnostics.fromPino()exists rather than argument-order autodetection. Pino takes(fields, message)— the reverse of this interface and ofconsole— so passing one directly type-checks and then silently drops every structured field. One explicit wrapper beats guesswork that's wrong for somebody.I also wrote and then deleted a
resolveLogger()doc comment claiming it detected argument order, which it didn't. Flagging it because the fix was to make the code match the doc, not the other way round.#729 — docs an agent can install from
llms.txtandAGENTS.md. Coding agents install dependencies now and the repo gave them nothing to read —docs/had exactly one file. This matters more for us than for competitors: our install path is the activation problem, and an agent that can install and verify without a human reading docs is the shortest version of it.Most of
AGENTS.mdis what not to do, because those are the expensive mistakes: don't enableenforceon a first install, don't invent an API key, don't addfilter()without one, don't leave a proxied app on the defaulttrustProxy, don't describeattackSignatures()as a WAF. It also insists the agent run theWebDecoy-Test/1.0one-liner and report what it returned — an install isn't finished until something visible happened.Both are public-safe: no metrics, no incidents, no internal specifics.
A real bug the docs caught
Writing the "how do I read a verdict" section surfaced an inconsistency I'd introduced in #31:
req.webdecoyis the detection in the Node adapters, while Hono'sc.get('webdecoy')was the decision. I'd written the doc claiming they were the same.Fixed in the code rather than the doc: all four adapters now also expose
webdecoyDecision, which means one thing everywhere.req.webdecoykeeps its existing meaning. Dropping theas anycasts that came with it lowered the budgets: express 12→10, nextjs 3→2.Verification
24 new tests, 430 total, 20/20 turbo tasks,
check:edgegreen on all three entry points.