I Wrote 238 Tests Against My Own Auth Package and Found 4 Real Bugs

2026年8月19日1 次浏览来源:Dev.to阅读原文

I'd already done a lot right by the time I started writing tests for Beaver-Auth.

Every module had gone through multiple rounds of deliberate review.

Enumeration protection, hashed tokens, refresh rotation, TOTP replay defense — the design was solid, and I knew it was solid, because I'd thought hard about every piece of it.

Then I wrote 238 tests against the actual code, and found 8 real bugs.

Some of them were the kind that would have silently broken production on day one.

This post isn't about the bugs specifically — it's about the gap between "I reviewed this carefully" and "this is shippable," and why that gap is bigger than most of us assume, even when the reviewing was genuinely careful. "Passing tests" and "shippable" are different claims Here's the trap I nearly walked into: I'd built a solid test suite covering the core auth flows — registration, login, verification — and every test passed.

It felt done.

But passing tests only tell you the code does what the tests expect.

If the tests were written from the same mental model as the code, they'll happily confirm a bug is correct behavior, because both the code and the test agree on the same wrong assumption.

The fix wasn't "write more tests." It was testing against the real, integrated system — not a hand-built mock of my own logic, and not testing modules in isolation from what actually calls them.

A few of the bugs below only surfaced because a test exercised the real dependency chain instead of assuming it worked.

Bug 1: TypeScript let an argument-shift bug compile clean This is the one that scared me most.

Beaver-Auth dispatches background work (like sending a verification email) through a interface: The default implementation had drifted to a different signature — missing the parameter entirely: Every caller still invoked it with all four arguments, matching the interface's shape.

Positionally, that meant the object — a plain data object — landed in the slot, where the code expected a function.

The real function landed in 's slot.

The real was silently dropped.

Run this, and throws — on literally every dispatch, in every deployment using the default dispatcher, which is the zero-config default nearly everyone would be using.

Every verification email.

Every password reset email.

Silently, forever. reported zero errors.

Not a warning, nothing.

Why?

TypeScript checks method parameters bivariantly by default — permissively enough that a function with fewer parameters can satisfy an interface expecting more, especially when one of the mismatched parameter types is (which structurally accepts anything).

The type system had a real hole here, and nothing about writing careful code would have caught it — only running the actual dispatch path caught it.

The fix was a one-line signature correction.

The lesson was: a compiling type signature is not proof the shapes actually agree at the call site.

I added a regression test that exercises this exact path through the real dispatcher (not a test double) specifically so this can never silently regress again.

Bug 2: A feature that looked complete but had no way to actually be called had a method — revoke a refresh-token family, kill a JWT session's ability to mint new access tokens.

Clean, well-tested in isolation.

Except: nothing, anywhere in the public API, ever returned a to the caller.

Login returned — no .

A consumer integrating this package had no way to obtain the one piece of information the logout function required.

The feature was fully implemented and completely unreachable.

This is the kind of gap that unit tests of in isolation will never catch — you'd construct the test by directly passing in a you already know, exactly like the broken consumer flow never could.

It only surfaced once I wrote an integration test that role-played an actual consumer: log in, then try to log out using only what the login response gave you.

That test couldn't be written without hitting the gap immediately.

The fix: thread through the type

分享
Baike.dev

baike.dev helps you discover great languages, frameworks, databases, DevOps and cloud-native tools.

Quick links

About

Contribute

Found a great developer tool? Share it with the community.

Submit a tool
© 2026 baike.dev Developer EncyclopediaUpdated daily · Discover great developer tools