pubky-testnet 0.9.0

A local test network for Pubky Core development.
Documentation

Pubky Testnet

A local test network for developing Pubky Core or applications depending on it.

All resources are ephemeral, including the database, and all servers are cleaned up when the testnet is dropped.

Quickstart

Option 1: Docker PostgreSQL (No External DB Required)

For testing without a separate Postgres installation, enable the docker-postgres feature:

[dev-dependencies]
pubky-testnet = { version = "0.9", features = ["docker-postgres"] }
# #[cfg(not(feature = "docker-postgres"))]
# fn main() {}
# #[cfg(feature = "docker-postgres")]
use pubky_testnet::EphemeralTestnet;

# #[cfg(feature = "docker-postgres")]
#[tokio::main]
async fn main() {
    let testnet = EphemeralTestnet::builder()
        .with_docker_postgres()
        .build()
        .await
        .unwrap();
}

This uses testcontainers to run PostgreSQL in a Docker container. Docker must be running on the host. The container is automatically cleaned up on drop and on Ctrl+C/SIGTERM.

Important: If you have multiple tests, see Sharing Docker Postgres Across Tests below.

Option 2: External PostgreSQL

If you prefer to use an external Postgres instance:

# Example local Postgres with password auth
docker run --name postgres \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=pubky_homeserver \
  -p 127.0.0.1:5432:5432 \
  -d postgres:18-alpine

Then run the testnet binary:

TEST_PUBKY_CONNECTION_STRING='postgres://postgres:postgres@localhost:5432/postgres?pubky-test=true' cargo run -p pubky-testnet

Usage

Writing Tests

use pubky_testnet::EphemeralTestnet;

#[tokio::test]
#[pubky_testnet::test] // Macro ensures ephemeral Postgres databases are cleaned up
async fn my_test() {
    // Run a new testnet. This creates a test DHT and homeserver.
    // By default, uses minimal_test_config() (admin/metrics disabled, no HTTP relay).
    let testnet = EphemeralTestnet::builder().build().await.unwrap();

    // Create a Pubky Http Client from the testnet.
    let client = testnet.client().unwrap();

    // Use the homeserver
    let homeserver = testnet.homeserver_app();
}

Custom Postgres Connection

By default (without docker-postgres), testnet will use postgres://localhost:5432/postgres?pubky-test=true. The ?pubky-test=true parameter indicates that the homeserver should create an ephemeral database.

To use a custom connection string:

Option A: Set the TEST_PUBKY_CONNECTION_STRING environment variable.

Option B: Pass the connection string programmatically:

use pubky_testnet::{Testnet, pubky_homeserver::ConnectionString};

#[tokio::main]
async fn main() {
    let connection_string = ConnectionString::new("postgres://localhost:5432/my_db").unwrap();
    let testnet = Testnet::new_with_custom_postgres(connection_string).await.unwrap();
}

Custom Configuration

use pubky_testnet::{EphemeralTestnet, pubky_homeserver::ConfigToml, pubky::Keypair};

#[tokio::main]
async fn main() {
    // Enable admin server for tests that need it
    let testnet = EphemeralTestnet::builder()
        .config(ConfigToml::default_test_config())
        .build()
        .await
        .unwrap();

    // Or use a custom keypair
    let testnet = EphemeralTestnet::builder()
        .keypair(Keypair::random())
        .build()
        .await
        .unwrap();

    // Enable HTTP relay for tests that need it
    let testnet = EphemeralTestnet::builder()
        .with_http_relay()
        .build()
        .await
        .unwrap();
    let http_relay = testnet.http_relay();
}

Sharing Docker Postgres Across Tests

When using docker-postgres, each call to .with_docker_postgres() starts a separate PostgreSQL container.

Use DockerPostgres::shared() to start one container and share its connection string across all tests. Docker handles cleanup automatically when the process exits.

use pubky_testnet::EphemeralTestnet;
use pubky_testnet::docker_postgres::DockerPostgres;

#[tokio::test]
async fn test_one() {
    let pg = DockerPostgres::shared().await;
    let testnet = EphemeralTestnet::builder()
        .postgres(pg.connection_string().unwrap())
        .build()
        .await
        .unwrap();
    // ... test code
}

#[tokio::test]
async fn test_two() {
    let pg = DockerPostgres::shared().await;
    let testnet = EphemeralTestnet::builder()
        .postgres(pg.connection_string().unwrap())
        .build()
        .await
        .unwrap();
    // ... test code
}

Each testnet still gets its own ephemeral database within the shared PostgreSQL instance, so tests remain isolated.

Troubleshooting

Docker not running

The docker-postgres feature requires Docker. If you see "Is Docker running?" errors, ensure the Docker daemon is started and your user has permission to access it (e.g., is in the docker group).

Docker Hub rate limits

The Postgres image is pulled from Docker Hub. Anonymous pulls are limited to 100 per 6 hours. If you hit this, either docker login or pre-pull the image:

docker pull postgres

Once cached locally, subsequent test runs won't pull again.

Binary (Static Testnet)

If you need to run the testnet in a separate process (e.g., to test Pubky Core in browsers), run the binary which creates these components with hardcoded configurations:

  1. A local DHT with bootstrapping nodes: &["localhost:6881"]
  2. A Pkarr Relay running on port 15411
  3. A Homeserver with address 8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo
  4. An HTTP relay running on port 15412