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 10SDK
Part 1: Set Up a Self-Hosted Test Instance
Option A: Using Mastodon (Recommended for Compatibility)
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
- Open http://solar.local:5000 (or your web interface)
- Create a new account/publisher named
solaruser - 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
-
In Mastodon:
- Go to http://mastodon.local:3001
- In search bar, type:
@solaruser@solar.local - Click the follow button
-
Verify in Solar Network:
# Check database for relationship
psql -d dyson_network -c \
"SELECT * FROM fediverse_relationships WHERE is_local_actor = true;"
-
Check Solar Network logs: Should see:
Processing activity type: Follow from actor: ... Processed follow from ... to ... -
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
- 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"
}'
-
Wait a few seconds
-
Check in Mastodon:
- Go to http://mastodon.local:3001
- The post should appear in the federated timeline
- It should show
@solaruser@solar.localas the author
-
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
-
In Mastodon:
- Find the Solar Network post
- Click the favorite/like button
-
Verify in Solar Network:
psql -d dyson_network -c \
"SELECT * FROM fediverse_reactions;"
- Check Solar Network logs:
Processing activity type: Like from actor: ... Processed like from ...
Test 7: Reply from Mastodon
Goal: Reply federates to Solar Network
-
In Mastodon:
- Reply to the Solar Network post
- Write: "@solaruser Nice to meet you!"
-
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:
- Check the signature header format
- Verify public key matches actor's keyId
- Ensure Date header is within 5 minutes
- Check host header matches request URL
Issue: "Target actor or inbox not found"
Cause: Remote actor not fetched yet
Solutions:
- Manually fetch the actor first
- Check actor URL is correct
- Verify remote instance is accessible
Issue: "Content already exists"
Cause: Duplicate activity received
Solutions:
- This is normal - deduplication is working
- Check if content appears correctly
Issue: CORS errors when testing from browser
Cause: Browser blocking cross-origin requests
Solutions:
- Use curl for API testing
- Or disable CORS in development
- 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
- 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
- Update Solar Network config:
{
"ActivityPub": {
"Domain": "your-domain.com",
"EnableFederation": true
}
}
-
Update DNS (if using real domain):
- Add A record pointing to your server
- Configure HTTPS (required for production federation)
-
Test WebFinger with your domain:
curl "https://your-domain.com/.well-known/webfinger?resource=acct:username@your-domain.com"
Test with Mastodon.social
-
Create a Mastodon.social account
- Go to https://mastodon.social
- Sign up for a test account
-
Search for your Solar Network user:
- In Mastodon.social search:
@username@your-domain.com - Click follow
- In Mastodon.social search:
-
Create a post in Solar Network
- Should appear in Mastodon.social
-
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
- Fix any issues found during testing
- Add retry logic for failed deliveries
- Implement activity queue for async processing
- Add monitoring and metrics
- Test with more instances (Pleroma, Pixelfed, etc.)
- Add support for more activity types
- Improve error handling and logging
- Add admin interface for managing federation