chrysalis #
A fork of haileyok/cocoon repurposed as a Go library for embedding an atproto PDS into other programs, plus a testpds tool for integration testing atproto apps in any language.
This fork is not intended to run a production PDS. For that, use upstream cocoon.
What this is for #
testpdslibrary (tangled.org/pdewey.com/chrysalis/testpds): spins up an ephemeral PDS and in-memory PLC directory inside a Go test.testpdsbinary (./cmd/testpds): runs the same stack as a standalone process. Paired with a Docker image, non-Go apps (TypeScript, Python, Rust) can run network-level integration tests against a throwaway PDS.
The bundled FakePLC serves the PLC directory read/write API over HTTP, so out-of-process clients can resolve did:plc: identities minted on the PDS. Signature verification and cross-service DID lookups work without a real PLC.
Go library usage #
import "tangled.org/pdewey.com/chrysalis/testpds"
func TestMyApp(t *testing.T) {
pds := testpds.StartT(t, nil)
alice := pds.MustCreateAccount(t, "alice.test", "alice@test.com", "password")
// alice is an authenticated *xrpc.Client. Use it like any atproto client.
_, err := atproto.RepoCreateRecord(ctx, alice, &atproto.RepoCreateRecord_Input{ ... })
// ...
}
testpds.StartT (test wrapper) and testpds.Start(ctx, opts) (non-test core) both return a *TestPDS exposing:
| field | description |
|---|---|
URL |
PDS base URL, e.g. http://localhost:41234 |
FakePLCURL |
PLC directory base URL, e.g. http://localhost:41235 |
DID |
PDS's own DID (did:web form) |
AdminPassword |
admin password for the instance |
FakePLC |
in-memory PLC client (for direct inspection) |
Docker usage (for non-Go apps) #
Docker use is still experimental
Build the image:
docker build -t chrysalis-testpds .
Run:
docker run --rm -p 2583:2583 -p 2582:2582 chrysalis-testpds \
--pds-host=localhost:2583
Or via compose:
docker compose up
Once running:
- PDS XRPC:
http://localhost:2583 - FakePLC directory:
http://localhost:2582
Point your client library's PLC resolver at http://localhost:2582 (e.g. @atproto/identity's new IdResolver({ plcUrl: ... })) and use the PDS normally.
Smoke test #
# Create an account
curl -X POST http://localhost:2583/xrpc/com.atproto.server.createAccount \
-H 'content-type: application/json' \
-d '{"handle":"alice.test","email":"alice@test.com","password":"hunter2"}'
# Resolve its DID doc
curl http://localhost:2582/did:plc:...
What works, what doesn't #
Works out of the box:
- All
com.atproto.server.*,com.atproto.repo.*,com.atproto.sync.*XRPC endpoints against the PDS. - Firehose via
com.atproto.sync.subscribeRepos(websocket). - Handle resolution for the PDS's own users.
- DID resolution via the FakePLC HTTP surface (DID doc, data, log, audit log).
- Session-based auth; OAuth DPoP flows.
Out of scope for a single-container test harness:
- Real-world handle resolution via DNS/
.well-knownfor handles not owned by this PDS. - Multi-PDS federation (would need multiple container instances and shared PLC).
- A real relay. If your test needs firehose fanout, run one alongside.
License #
MIT, same as upstream cocoon.