Cocoon fork designed for use in app integration tests
go atproto
Go 99%
Makefile <1%
Dockerfile <1%

README.md

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 #

  • testpds library (tangled.org/pdewey.com/chrysalis/testpds): spins up an ephemeral PDS and in-memory PLC directory inside a Go test.
  • testpds binary (./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-known for 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.