Files
App/docs/activitypub/ACTIVITYPUB_TESTING_GUIDE.md

19 KiB

ActivityPub Testing Guide for Solar Network

This guide will help you test the ActivityPub implementation in Solar Network, starting with a self-hosted instance and then moving to a real instance.

Prerequisites

  • Solar Network codebase with ActivityPub implementation
  • Docker installed (for running Mastodon/Fediverse instances)
  • PostgreSQL database running
  • .NET 10 SDK

Part 1: Set Up a Self-Hosted Test Instance

1. Create a Docker Compose File

Create docker-compose.mastodon-test.yml:

version: '3'

services:
  db:
    restart: always
    image: postgres:14-alpine
    environment:
      POSTGRES_USER: mastodon
      POSTGRES_PASSWORD: mastodon_password
      POSTGRES_DB: mastodon
    networks:
      - mastodon_network
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "mastodon"]
      interval: 5s
      retries: 5

  redis:
    restart: always
    image: redis:7-alpine
    networks:
      - mastodon_network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      retries: 5

  es:
    restart: always
    image: docker.elastic.co/elasticsearch:8.10.2
    environment:
      - "discovery.type=single-node"
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
      - "xpack.security.enabled=false"
    networks:
      - mastodon_network
    healthcheck:
      test: ["CMD-SHELL", "curl -silent http://localhost:9200/_cluster/health || exit 1"]
      interval: 10s
      retries: 10

  web:
    restart: always
    image: tootsuite/mastodon:latest
    env_file: .env.mastodon
    command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
    ports:
      - "3001:3000"
    depends_on:
      - db
      - redis
      - es
    networks:
      - mastodon_network
    volumes:
      - ./mastodon-data/public:/mastodon/public/system

  streaming:
    restart: always
    image: tootsuite/mastodon:latest
    env_file: .env.mastodon
    command: node ./streaming
    ports:
      - "4000:4000"
    depends_on:
      - db
      - redis
    networks:
      - mastodon_network

  sidekiq:
    restart: always
    image: tootsuite/mastodon:latest
    env_file: .env.mastodon
    command: bundle exec sidekiq
    depends_on:
      - db
      - redis
    networks:
      - mastodon_network

networks:
  mastodon_network:
    driver: bridge

2. Create Environment File

Create .env.mastodon:

# Federation
LOCAL_DOMAIN=mastodon.local
LOCAL_HTTPS=false

# Database
DB_HOST=db
DB_PORT=5432
DB_USER=mastodon
DB_NAME=mastodon
DB_PASS=mastodon_password

# Redis
REDIS_HOST=redis
REDIS_PORT=6379

# Elasticsearch
ES_ENABLED=true
ES_HOST=es
ES_PORT=9200

# Secrets (generate these!)
SECRET_KEY_BASE=change_me_to_a_random_string_at_least_32_chars
OTP_SECRET=change_me_to_another_random_string

# Defaults
SINGLE_USER_MODE=false
DEFAULT_LOCALE=en

Generate secrets:

# Run these to generate random secrets
openssl rand -base64 32

3. Start Mastodon

docker-compose -f docker-compose.mastodon-test.yml up -d

# Check logs
docker-compose -f docker-compose.mastodon-test.yml logs -f web

Wait for the web service to be healthy (may take 2-5 minutes).

4. Create a Mastodon Account

# Run this command to create an admin account
docker-compose -f docker-compose.mastodon-test.yml exec web \
  bin/tootctl accounts create \
  testuser \
  testuser@mastodon.local \
  --email=test@example.com \
  --confirmed \
  --role=admin \
  --approve

Set password: TestPassword123!

5. Update Your /etc/hosts

sudo nano /etc/hosts

Add:

127.0.0.1   mastodon.local
127.0.0.1   solar.local

Option B: Using GoToSocial (Lightweight Alternative)

Create docker-compose.gotosocial.yml:

version: '3'

services:
  gotosocial:
    image: superseriousbusiness/gotosocial:latest
    environment:
      - GTS_HOST=gotosocial.local
      - GTS_ACCOUNT_DOMAIN=gotosocial.local
      - GTS_PROTOCOL=http
      - GTS_DB_TYPE=sqlite
      - GTS_DB_ADDRESS=/gotosocial/data/sqlite.db
      - GTS_STORAGE_LOCAL_BASE_PATH=/gotosocial/data/storage
    ports:
      - "3002:8080"
    volumes:
      - ./gotosocial-data:/gotosocial/data

networks:
  default:

Start it:

docker-compose -f docker-compose.gotosocial.yml up -d

Create account:

docker-compose -f docker-compose.gotosocial.yml exec gotosocial \
  /gotosocial/gotosocial admin account create \
  --username testuser \
  --email test@example.com \
  --password TestPassword123!

Part 2: Configure Solar Network for Federation

1. Update appsettings.json

Edit DysonNetwork.Sphere/appsettings.json:

{
  "ActivityPub": {
    "Domain": "solar.local",
    "EnableFederation": true
  },
  "Kestrel": {
    "Endpoints": {
      "Http": {
        "Url": "http://solar.local:5000"
      }
    }
  }
}

2. Update /etc/hosts

Add both instances:

127.0.0.1   mastodon.local
127.0.0.1   solar.local
127.0.0.1   gotosocial.local

3. Apply Database Migrations

cd DysonNetwork.Sphere
dotnet ef database update

4. Start Solar Network

dotnet run --project DysonNetwork.Sphere

Solar Network should now be running on http://solar.local:5000

Part 3: Create Test Users

In Solar Network

  1. Open http://solar.local:5000 (or your web interface)
  2. Create a new account/publisher named solaruser
  3. Note down the publisher ID for later

Or via API (if you have an existing account):

# First, create a publisher in Solar Network
curl -X POST http://solar.local:5000/api/publishers \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{
    "name": "solaruser",
    "nick": "Solar User",
    "bio": "Testing ActivityPub federation!",
    "type": 0
  }'

In Mastodon

Open http://mastodon.local:3001 and log in with:

  • Username: testuser
  • Password: TestPassword123!

Part 4: Test Federation Scenarios

Test 1: WebFinger Discovery

Goal: Verify Solar Network is discoverable

# Query Solar Network's WebFinger endpoint
curl -v "http://solar.local:5000/.well-known/webfinger?resource=acct:solaruser@solar.local"

# Expected response (200 OK):
{
  "subject": "acct:solaruser@solar.local",
  "links": [
    {
      "rel": "self",
      "type": "application/activity+json",
      "href": "https://solar.local:5000/activitypub/actors/solaruser"
    },
    {
      "rel": "http://webfinger.net/rel/profile-page",
      "type": "text/html",
      "href": "https://solar.local:5000/users/solaruser"
    }
  ]
}

Test 2: Fetch Actor Profile

Goal: Get ActivityPub actor JSON

# Fetch Solar Network actor from Mastodon
curl -H "Accept: application/activity+json" \
  http://solar.local:5000/activitypub/actors/solaruser

# Expected response includes:
{
  "@context": ["https://www.w3.org/ns/activitystreams"],
  "id": "https://solar.local:5000/activitypub/actors/solaruser",
  "type": "Person",
  "preferredUsername": "solaruser",
  "inbox": "https://solar.local:5000/activitypub/actors/solaruser/inbox",
  "outbox": "https://solar.local:5000/activitypub/actors/solaruser/outbox",
  "followers": "https://solar.local:5000/activitypub/actors/solaruser/followers",
  "publicKey": {
    "id": "https://solar.local:5000/activitypub/actors/solaruser#main-key",
    "owner": "https://solar.local:5000/activitypub/actors/solaruser",
    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\n..."
  }
}

Test 3: Follow from Mastodon to Solar Network

Goal: Mastodon user follows Solar Network user

  1. In Mastodon:

  2. Verify in Solar Network:

# Check database for relationship
psql -d dyson_network -c \
  "SELECT * FROM fediverse_relationships WHERE is_local_actor = true;"
  1. Check Solar Network logs: Should see:

    Processing activity type: Follow from actor: ...
    Processed follow from ... to ...
    
  2. Verify Mastodon receives Accept:

    • Check Mastodon logs for Accept activity
    • Verify follow appears as accepted in Mastodon

Test 4: Follow from Solar Network to Mastodon

Goal: Solar Network user follows Mastodon user

You'll need to call the ActivityPub delivery service:

# Via API (you'll need to implement this endpoint):
curl -X POST http://solar.local:5000/api/activitypub/follow \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{
    "targetActorUri": "http://mastodon.local:3001/users/testuser"
  }'

Or test directly with curl (simulating a Follow activity):

# Create a Follow activity
curl -X POST http://solar.local:5000/activitypub/actors/solaruser/inbox \
  -H "Content-Type: application/activity+json" \
  -d '{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "http://mastodon.local:3001/test-follow-activity",
    "type": "Follow",
    "actor": "http://mastodon.local:3001/users/testuser",
    "object": "https://solar.local:5000/activitypub/actors/solaruser"
  }'

Test 5: Create a Post in Solar Network

Goal: Post federates to Mastodon

  1. Create a post via Solar Network API:
curl -X POST http://solar.local:5000/api/posts \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{
    "content": "Hello fediverse! Testing ActivityPub from Solar Network! 🚀",
    "visibility": 0,
    "publisherId": "PUBLISHER_ID"
  }'
  1. Wait a few seconds

  2. Check in Mastodon:

    • Go to http://mastodon.local:3001
    • The post should appear in the federated timeline
    • It should show @solaruser@solar.local as the author
  3. Verify Solar Network logs:

    Successfully sent activity to http://mastodon.local:3001/inbox
    

Test 6: Like from Mastodon

Goal: Mastodon user likes a Solar Network post

  1. In Mastodon:

    • Find the Solar Network post
    • Click the favorite/like button
  2. Verify in Solar Network:

psql -d dyson_network -c \
  "SELECT * FROM fediverse_reactions;"
  1. Check Solar Network logs:
    Processing activity type: Like from actor: ...
    Processed like from ...
    

Test 7: Reply from Mastodon

Goal: Reply federates to Solar Network

  1. In Mastodon:

    • Reply to the Solar Network post
    • Write: "@solaruser Nice to meet you!"
  2. Verify in Solar Network:

psql -d dyson_network -c \
  "SELECT * FROM fediverse_contents WHERE in_reply_to IS NOT NULL;"

Part 5: Debugging and Troubleshooting

Enable Detailed Logging

Edit appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "DysonNetwork.Sphere.ActivityPub": "Trace"
    }
  }
}

Check Database State

# Check actors
psql -d dyson_network -c \
  "SELECT uri, username, display_name FROM fediverse_actors;"

# Check contents
psql -d dyson_network -c \
  "SELECT uri, type, content FROM fediverse_contents;"

# Check relationships
psql -d dyson_network -c \
  "SELECT * FROM fediverse_relationships;"

# Check activities
psql -d dyson_network -c \
  "SELECT type, status, error_message FROM fediverse_activities;"

# Check failed activities
psql -d dyson_network -c \
  "SELECT * FROM fediverse_activities WHERE status = 3;"  # 3 = Failed

Common Issues

Issue: "Failed to verify signature"

Cause: HTTP Signature verification failed

Solutions:

  1. Check the signature header format
  2. Verify public key matches actor's keyId
  3. Ensure Date header is within 5 minutes
  4. Check host header matches request URL

Issue: "Target actor or inbox not found"

Cause: Remote actor not fetched yet

Solutions:

  1. Manually fetch the actor first
  2. Check actor URL is correct
  3. Verify remote instance is accessible

Issue: "Content already exists"

Cause: Duplicate activity received

Solutions:

  1. This is normal - deduplication is working
  2. Check if content appears correctly

Issue: CORS errors when testing from browser

Cause: Browser blocking cross-origin requests

Solutions:

  1. Use curl for API testing
  2. Or disable CORS in development
  3. Test directly from Mastodon interface

View HTTP Signatures

For debugging, you can inspect the signature:

# From Mastodon to Solar Network
curl -v -X POST http://solar.local:5000/activitypub/actors/solaruser/inbox \
  -H "Content-Type: application/activity+json" \
  -d '{"type":"Follow",...}'

Look for the Signature header in the output.

Test HTTP Signature Verification Manually

Create a test script test-signature.js:

const crypto = require('crypto');

// Test signature verification
const publicKey = `-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----`;

const signingString = `(request-target): post /activitypub/actors/solaruser/inbox
host: solar.local:5000
date: ${new Date().toUTCString()}
content-length: ...`;

const signature = '...';

const verify = crypto.createVerify('SHA256');
verify.update(signingString);
const isValid = verify.verify(publicKey, signature, 'base64');

console.log('Signature valid:', isValid);

Part 6: Test with a Real Instance

Preparing for Public Federation

  1. Get a real domain (e.g., via ngrok or a VPS)
# Using ngrok for testing
ngrok http 5000

# This gives you: https://random-id.ngrok-free.app
  1. Update Solar Network config:
{
  "ActivityPub": {
    "Domain": "your-domain.com",
    "EnableFederation": true
  }
}
  1. Update DNS (if using real domain):

    • Add A record pointing to your server
    • Configure HTTPS (required for production federation)
  2. Test WebFinger with your domain:

curl "https://your-domain.com/.well-known/webfinger?resource=acct:username@your-domain.com"

Test with Mastodon.social

  1. Create a Mastodon.social account

  2. Search for your Solar Network user:

    • In Mastodon.social search: @username@your-domain.com
    • Click follow
  3. Create a post in Solar Network

    • Should appear in Mastodon.social
  4. Reply from Mastodon.social

    • Should appear in Solar Network

Test with Other Instances

  • Pleroma: Similar to Mastodon, good for testing
  • Lemmy: For testing community features (later)
  • Pixelfed: For testing media posts
  • PeerTube: For testing video content (later)

Part 7: Verification Checklist

Self-Hosted Instance Tests

  • WebFinger returns correct actor links
  • Actor profile has all required fields
  • Follow from Mastodon to Solar Network works
  • Follow from Solar Network to Mastodon works
  • Accept activity sent back to Mastodon
  • Posts from Solar Network appear in Mastodon timeline
  • Posts from Mastodon appear in Solar Network database
  • Likes from Mastodon appear in Solar Network
  • Replies from Mastodon appear in Solar Network
  • Keys are properly generated and stored
  • HTTP signatures are correctly verified
  • Outbox returns public posts

Real Instance Tests

  • Domain is publicly accessible
  • HTTPS is working (or HTTP for local testing)
  • WebFinger works with your domain
  • Actor is discoverable from other instances
  • Posts federate to public instances
  • Users can follow across instances
  • Timelines show federated content

Part 8: Monitoring During Tests

Check Solar Network Logs

# Follow logs in real-time
dotnet run --project DysonNetwork.Sphere | grep -i activitypub

Check Mastodon Logs

docker-compose -f docker-compose.mastodon-test.yml logs -f web | grep -i federation

Monitor Database Activity

# Watch activity table
watch -n 2 'psql -d dyson_network -c "SELECT type, status, created_at FROM fediverse_activities ORDER BY created_at DESC LIMIT 10;"'

Check Network Traffic

# Monitor HTTP requests
tcpdump -i lo port 5000 or port 3001 -A

Part 9: Advanced Testing

Test HTTP Signature Fallbacks

Test with various signature headers:

# With Date header
curl -H "Date: $(date -u +%a,\ %d\ %b\ %Y\ %T\ GMT)" ...

# With Digest header
curl -H "Digest: SHA-256=$(echo -n '{}' | openssl dgst -sha256 -binary | base64)" ...

# Multiple signed headers
curl -H "Signature: keyId=\"...\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest\",signature=\"...\"" ...

Test Rate Limiting

Send multiple requests quickly:

for i in {1..10}; do
  curl -X POST http://solar.local:5000/activitypub/actors/solaruser/inbox \
    -H "Content-Type: application/activity+json" \
    -d '{"type":"Create",...}'
done

Test Large Posts

Send post with attachments:

curl -X POST http://solar.local:5000/api/posts \
  -H "Content-Type: application/json" \
  -d '{
    "content": "A post with an image",
    "attachments": [{"id": "file-id"}],
    "visibility": 0
  }'

Part 10: Cleanup

Stop Test Instances

# Stop Mastodon
docker-compose -f docker-compose.mastodon-test.yml down

# Stop GoToSocial
docker-compose -f docker-compose.gotosocial.yml down

# Remove data volumes
docker-compose -f docker-compose.mastodon-test.yml down -v

Reset Solar Network Database

# Warning: This deletes all data!
cd DysonNetwork.Sphere
dotnet ef database drop
dotnet ef database update

Remove /etc/hosts Entries

sudo nano /etc/hosts

# Remove these lines:
# 127.0.0.1   mastodon.local
# 127.0.0.1   solar.local
# 127.0.0.1   gotosocial.local

Next Steps After Testing

  1. Fix any issues found during testing
  2. Add retry logic for failed deliveries
  3. Implement activity queue for async processing
  4. Add monitoring and metrics
  5. Test with more instances (Pleroma, Pixelfed, etc.)
  6. Add support for more activity types
  7. Improve error handling and logging
  8. Add admin interface for managing federation

Useful Tools

ActivityPub Testing Tools

HTTP Testing

JSON Inspection

Network Debugging

References