From 9f4a7a3fe89a392c37ebb8455650262efecc6f08 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 28 Dec 2025 18:26:23 +0800 Subject: [PATCH] :hammer: Add some tests utilities of activity pub --- .env.testing.example | 43 + .../ACTIVITYPUB_IMPLEMENTATION.md | 0 .../ACTIVITYPUB_PLAN.md | 0 .../ACTIVITYPUB_SUMMARY.md | 0 docs/ACTIVITYPUB_TESTING_GUIDE.md | 820 ++++++++++++++++++ docs/ACTIVITYPUB_TESTING_HELPER_API.md | 506 +++++++++++ docs/ACTIVITYPUB_TESTING_INDEX.md | 448 ++++++++++ docs/ACTIVITYPUB_TESTING_QUICKREF.md | 356 ++++++++ docs/ACTIVITYPUB_TESTING_QUICKSTART.md | 289 ++++++ docs/ACTIVITYPUB_TESTING_README.md | 275 ++++++ docs/ACTIVITYPUB_TEST_RESULTS_TEMPLATE.md | 282 ++++++ setup-activitypub-test.sh | 303 +++++++ test-activitypub.sh | 155 ++++ 13 files changed, 3477 insertions(+) create mode 100644 .env.testing.example rename ACTIVITYPUB_IMPLEMENTATION.md => docs/ACTIVITYPUB_IMPLEMENTATION.md (100%) rename ACTIVITYPUB_PLAN.md => docs/ACTIVITYPUB_PLAN.md (100%) rename ACTIVITYPUB_SUMMARY.md => docs/ACTIVITYPUB_SUMMARY.md (100%) create mode 100644 docs/ACTIVITYPUB_TESTING_GUIDE.md create mode 100644 docs/ACTIVITYPUB_TESTING_HELPER_API.md create mode 100644 docs/ACTIVITYPUB_TESTING_INDEX.md create mode 100644 docs/ACTIVITYPUB_TESTING_QUICKREF.md create mode 100644 docs/ACTIVITYPUB_TESTING_QUICKSTART.md create mode 100644 docs/ACTIVITYPUB_TESTING_README.md create mode 100644 docs/ACTIVITYPUB_TEST_RESULTS_TEMPLATE.md create mode 100755 setup-activitypub-test.sh create mode 100755 test-activitypub.sh diff --git a/.env.testing.example b/.env.testing.example new file mode 100644 index 0000000..9665131 --- /dev/null +++ b/.env.testing.example @@ -0,0 +1,43 @@ +# ActivityPub Testing Environment Variables + +# Solar Network Configuration +SOLAR_DOMAIN=solar.local +SOLAR_PORT=5000 +SOLAR_URL=http://solar.local:5000 + +# Mastodon (Self-Hosted Test Instance) +MASTODON_DOMAIN=mastodon.local +MASTODON_PORT=3001 +MASTODON_STREAMING_PORT=4000 +MASTODON_URL=http://mastodon.local:3001 + +# Database +DB_CONNECTION_STRING=Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres + +# Test Accounts +SOLAR_TEST_USERNAME=solaruser +MASTODON_TEST_USERNAME=testuser +MASTODON_TEST_PASSWORD=TestPassword123! + +# ActivityPub Settings +ACTIVITYPUB_DOMAIN=solar.local +ACTIVITYPUB_ENABLE_FEDERATION=true +ACTIVITYPUB_SIGNATURE_ALGORITHM=rsa-sha256 + +# HTTP Settings +HTTP_TIMEOUT=30 +HTTP_MAX_RETRIES=3 + +# Logging +LOG_LEVEL=Debug +ACTIVITYPUB_LOG_LEVEL=Trace + +# Testing +TEST_SKIP_DATABASE_RESET=false +TEST_SKIP_MASTODON_SETUP=false +TEST_AUTO_ACCEPT_FOLLOWS=false + +# Development (only in dev environment) +DEV_DISABLE_SIGNATURE_VERIFICATION=false +DEV_LOG_HTTP_BODIES=false +DEV_DISABLE_CORS=false diff --git a/ACTIVITYPUB_IMPLEMENTATION.md b/docs/ACTIVITYPUB_IMPLEMENTATION.md similarity index 100% rename from ACTIVITYPUB_IMPLEMENTATION.md rename to docs/ACTIVITYPUB_IMPLEMENTATION.md diff --git a/ACTIVITYPUB_PLAN.md b/docs/ACTIVITYPUB_PLAN.md similarity index 100% rename from ACTIVITYPUB_PLAN.md rename to docs/ACTIVITYPUB_PLAN.md diff --git a/ACTIVITYPUB_SUMMARY.md b/docs/ACTIVITYPUB_SUMMARY.md similarity index 100% rename from ACTIVITYPUB_SUMMARY.md rename to docs/ACTIVITYPUB_SUMMARY.md diff --git a/docs/ACTIVITYPUB_TESTING_GUIDE.md b/docs/ACTIVITYPUB_TESTING_GUIDE.md new file mode 100644 index 0000000..ea94bb2 --- /dev/null +++ b/docs/ACTIVITYPUB_TESTING_GUIDE.md @@ -0,0 +1,820 @@ +# 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 + +### Option A: Using Mastodon (Recommended for Compatibility) + +#### 1. Create a Docker Compose File + +Create `docker-compose.mastodon-test.yml`: + +```yaml +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`: + +```bash +# 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:** +```bash +# Run these to generate random secrets +openssl rand -base64 32 +``` + +#### 3. Start Mastodon + +```bash +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 + +```bash +# 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 + +```bash +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`: + +```yaml +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: +```bash +docker-compose -f docker-compose.gotosocial.yml up -d +``` + +Create account: +```bash +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`: + +```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 + +```bash +cd DysonNetwork.Sphere +dotnet ef database update +``` + +### 4. Start Solar Network + +```bash +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): + +```bash +# 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 + +```bash +# 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 + +```bash +# 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**: + - Go to http://mastodon.local:3001 + - In search bar, type: `@solaruser@solar.local` + - Click the follow button + +2. **Verify in Solar Network**: +```bash +# Check database for relationship +psql -d dyson_network -c \ + "SELECT * FROM fediverse_relationships WHERE is_local_actor = true;" +``` + +3. **Check Solar Network logs**: + Should see: + ``` + Processing activity type: Follow from actor: ... + Processed follow from ... to ... + ``` + +4. **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: + +```bash +# 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): + +```bash +# 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**: +```bash +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" + }' +``` + +2. **Wait a few seconds** + +3. **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 + +4. **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**: +```bash +psql -d dyson_network -c \ + "SELECT * FROM fediverse_reactions;" +``` + +3. **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**: +```bash +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`: + +```json +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "DysonNetwork.Sphere.ActivityPub": "Trace" + } + } +} +``` + +### Check Database State + +```bash +# 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: + +```bash +# 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`: + +```javascript +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) + +```bash +# Using ngrok for testing +ngrok http 5000 + +# This gives you: https://random-id.ngrok-free.app +``` + +2. **Update Solar Network config**: + +```json +{ + "ActivityPub": { + "Domain": "your-domain.com", + "EnableFederation": true + } +} +``` + +3. **Update DNS** (if using real domain): + - Add A record pointing to your server + - Configure HTTPS (required for production federation) + +4. **Test WebFinger with your domain**: + +```bash +curl "https://your-domain.com/.well-known/webfinger?resource=acct:username@your-domain.com" +``` + +### Test with Mastodon.social + +1. **Create a Mastodon.social account** + - Go to https://mastodon.social + - Sign up for a test 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 + +```bash +# Follow logs in real-time +dotnet run --project DysonNetwork.Sphere | grep -i activitypub +``` + +### Check Mastodon Logs + +```bash +docker-compose -f docker-compose.mastodon-test.yml logs -f web | grep -i federation +``` + +### Monitor Database Activity + +```bash +# 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 + +```bash +# 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: + +```bash +# 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: + +```bash +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: + +```bash +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 + +```bash +# 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 + +```bash +# Warning: This deletes all data! +cd DysonNetwork.Sphere +dotnet ef database drop +dotnet ef database update +``` + +### Remove /etc/hosts Entries + +```bash +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 +- [ActivityPub Playground](https://swicth.github.io/activity-pub-playground/) +- [FediTest](https://feditest.com/) +- [FediVerse.net](https://fedi.net/) + +### HTTP Testing +- [curl](https://curl.se/) +- [httpie](https://httpie.io/) +- [Postman](https://www.postman.com/) + +### JSON Inspection +- [jq](https://stedolan.github.io/jq/) +- [jsonpath.com](https://jsonpath.com/) + +### Network Debugging +- [Wireshark](https://www.wireshark.org/) +- [tcpdump](https://www.tcpdump.org/) + +## References + +- [ActivityPub W3C Spec](https://www.w3.org/TR/activitypub/) +- [HTTP Signatures Draft](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures) +- [WebFinger RFC 7033](https://tools.ietf.org/html/rfc7033) +- [Mastodon Federation Documentation](https://docs.joinmastodon.org/admin/federation/) diff --git a/docs/ACTIVITYPUB_TESTING_HELPER_API.md b/docs/ACTIVITYPUB_TESTING_HELPER_API.md new file mode 100644 index 0000000..78e7acc --- /dev/null +++ b/docs/ACTIVITYPUB_TESTING_HELPER_API.md @@ -0,0 +1,506 @@ +# ActivityPub Testing Helper API + +This document describes helper endpoints for testing ActivityPub federation. + +## Purpose + +These endpoints allow you to manually trigger ActivityPub activities for testing purposes without implementing the full UI federation integration yet. + +## Helper Endpoints + +### 1. Send Follow Activity + +**Endpoint**: `POST /api/activitypub/test/follow` + +**Description**: Sends a Follow activity to a remote actor + +**Request Body**: +```json +{ + "targetActorUri": "http://mastodon.local:3001/users/testuser" +} +``` + +**Response**: +```json +{ + "success": true, + "activityId": "http://solar.local:5000/activitypub/activities/...", + "targetActor": "http://mastodon.local:3001/users/testuser" +} +``` + +### 2. Send Like Activity + +**Endpoint**: `POST /api/activitypub/test/like` + +**Description**: Sends a Like activity for a post (can be local or remote) + +**Request Body**: +```json +{ + "postId": "POST_ID", + "targetActorUri": "http://mastodon.local:3001/users/testuser" +} +``` + +**Response**: +```json +{ + "success": true, + "activityId": "http://solar.local:5000/activitypub/activities/..." +} +``` + +### 3. Send Announce (Boost) Activity + +**Endpoint**: `POST /api/activitypub/test/announce` + +**Description**: Boosts a post to followers + +**Request Body**: +```json +{ + "postId": "POST_ID" +} +``` + +### 4. Send Undo Activity + +**Endpoint**: `POST /api/activitypub/test/undo` + +**Description**: Undoes a previous activity + +**Request Body**: +```json +{ + "activityType": "Like", // or "Follow", "Announce" + "objectUri": "http://solar.local:5000/activitypub/objects/POST_ID" +} +``` + +### 5. Get Federation Status + +**Endpoint**: `GET /api/activitypub/test/status` + +**Description**: Returns current federation statistics + +**Response**: +```json +{ + "actors": { + "total": 5, + "local": 1, + "remote": 4 + }, + "contents": { + "total": 25, + "byType": { + "Note": 20, + "Article": 5 + } + }, + "relationships": { + "total": 8, + "accepted": 6, + "pending": 1, + "rejected": 1 + }, + "activities": { + "total": 45, + "byStatus": { + "Completed": 40, + "Pending": 3, + "Failed": 2 + }, + "byType": { + "Create": 20, + "Follow": 8, + "Accept": 6, + "Like": 5, + "Announce": 3, + "Undo": 2, + "Delete": 1 + } + } +} +``` + +### 6. Get Recent Activities + +**Endpoint**: `GET /api/activitypub/test/activities` + +**Query Parameters**: +- `limit`: Number of activities to return (default: 20) +- `type`: Filter by activity type (optional) + +**Response**: +```json +{ + "activities": [ + { + "id": "ACTIVITY_ID", + "type": "Follow", + "status": "Completed", + "actorUri": "http://mastodon.local:3001/users/testuser", + "objectUri": "http://solar.local:5000/activitypub/actors/solaruser", + "createdAt": "2024-01-15T10:30:00Z", + "errorMessage": null + } + ] +} +``` + +### 7. Get Actor Keys + +**Endpoint**: `GET /api/activitypub/test/actors/{username}/keys` + +**Description**: Returns the public/private key pair for a publisher + +**Response**: +```json +{ + "username": "solaruser", + "hasKeys": true, + "actorUri": "http://solar.local:5000/activitypub/actors/solaruser", + "publicKeyId": "http://solar.local:5000/activitypub/actors/solaruser#main-key", + "publicKey": "-----BEGIN PUBLIC KEY-----\n...", + "privateKeyStored": true +} +``` + +### 8. Test HTTP Signature + +**Endpoint**: `POST /api/activitypub/test/sign` + +**Description**: Test if a signature string is valid for a given public key + +**Request Body**: +```json +{ + "publicKey": "-----BEGIN PUBLIC KEY-----\n...", + "signingString": "(request-target): post /inbox\nhost: example.com\ndate: ...", + "signature": "..." +} +``` + +**Response**: +```json +{ + "valid": true, + "message": "Signature is valid" +} +``` + +## Controller Implementation + +Create `DysonNetwork.Sphere/ActivityPub/ActivityPubTestController.cs`: + +```csharp +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace DysonNetwork.Sphere.ActivityPub; + +[ApiController] +[Route("api/activitypub/test")] +[Authorize] // Require auth for testing +public class ActivityPubTestController( + AppDatabase db, + ActivityPubDeliveryService deliveryService, + ActivityPubKeyService keyService, + ActivityPubSignatureService signatureService, + IConfiguration configuration, + ILogger logger +) : ControllerBase +{ + [HttpPost("follow")] + public async Task TestFollow([FromBody] TestFollowRequest request) + { + var currentUser = GetCurrentUser(); + var publisher = await GetPublisherForUser(currentUser.Id); + + if (publisher == null) + return BadRequest("Publisher not found"); + + var success = await deliveryService.SendFollowActivityAsync( + publisher.Id, + request.TargetActorUri + ); + + return Ok(new + { + success, + targetActorUri = request.TargetActorUri, + publisherId = publisher.Id + }); + } + + [HttpPost("like")] + public async Task TestLike([FromBody] TestLikeRequest request) + { + var currentUser = GetCurrentUser(); + var publisher = await GetPublisherForUser(currentUser.Id); + + var success = await deliveryService.SendLikeActivityAsync( + request.PostId, + currentUser.Id, + request.TargetActorUri + ); + + return Ok(new { success, postId = request.PostId }); + } + + [HttpPost("announce")] + public async Task TestAnnounce([FromBody] TestAnnounceRequest request) + { + var post = await db.Posts.FindAsync(request.PostId); + if (post == null) + return NotFound(); + + var success = await deliveryService.SendCreateActivityAsync(post); + + return Ok(new { success, postId = request.PostId }); + } + + [HttpPost("undo")] + public async Task TestUndo([FromBody] TestUndoRequest request) + { + var currentUser = GetCurrentUser(); + var publisher = await GetPublisherForUser(currentUser.Id); + + if (publisher == null) + return BadRequest("Publisher not found"); + + var success = await deliveryService.SendUndoActivityAsync( + request.ActivityType, + request.ObjectUri, + publisher.Id + ); + + return Ok(new { success, activityType = request.ActivityType }); + } + + [HttpGet("status")] + public async Task GetStatus() + { + var totalActors = await db.FediverseActors.CountAsync(); + var localActors = await db.FediverseActors + .CountAsync(a => a.Uri.Contains("solar.local")); + + var totalContents = await db.FediverseContents.CountAsync(); + + var relationships = await db.FediverseRelationships + .GroupBy(r => r.State) + .Select(g => new { State = g.Key, Count = g.Count() }) + .ToListAsync(); + + var activitiesByStatus = await db.FediverseActivities + .GroupBy(a => a.Status) + .Select(g => new { Status = g.Key, Count = g.Count() }) + .ToListAsync(); + + var activitiesByType = await db.FediverseActivities + .GroupBy(a => a.Type) + .Select(g => new { Type = g.Key, Count = g.Count() }) + .ToListAsync(); + + return Ok(new + { + actors = new + { + total = totalActors, + local = localActors, + remote = totalActors - localActors + }, + contents = new + { + total = totalContents + }, + relationships = relationships.ToDictionary(r => r.State.ToString(), r => r.Count), + activities = new + { + byStatus = activitiesByStatus.ToDictionary(a => a.Status.ToString(), a => a.Count), + byType = activitiesByType.ToDictionary(a => a.Type.ToString(), a => a.Count) + } + }); + } + + [HttpGet("activities")] + public async Task GetActivities([FromQuery] int limit = 20, [FromQuery] string? type = null) + { + var query = db.FediverseActivities + .OrderByDescending(a => a.CreatedAt); + + if (!string.IsNullOrEmpty(type)) + { + query = query.Where(a => a.Type.ToString() == type); + } + + var activities = await query + .Take(limit) + .Select(a => new + { + a.Id, + a.Type, + a.Status, + ActorUri = a.Actor.Uri, + ObjectUri = a.ObjectUri, + a.CreatedAt, + a.ErrorMessage + }) + .ToListAsync(); + + return Ok(new { activities }); + } + + [HttpGet("actors/{username}/keys")] + public async Task GetActorKeys(string username) + { + var publisher = await db.Publishers + .FirstOrDefaultAsync(p => p.Name == username); + + if (publisher == null) + return NotFound(); + + var actorUrl = $"http://solar.local:5000/activitypub/actors/{username}"; + + var (privateKey, publicKey) = keyService.GenerateKeyPair(); + + return Ok(new + { + username, + hasKeys = publisher.Meta != null, + actorUri, + publicKeyId = $"{actorUrl}#main-key", + publicKey = publicKey, + privateKeyStored = publisher.Meta != null + }); + } + + [HttpPost("sign")] + public ActionResult TestSignature([FromBody] TestSignatureRequest request) + { + var isValid = keyService.Verify( + request.PublicKey, + request.SigningString, + request.Signature + ); + + return Ok(new + { + valid = isValid, + message = isValid ? "Signature is valid" : "Signature is invalid" + }); + } + + private async Task GetPublisherForUser(Guid accountId) + { + return await db.Publishers + .Include(p => p.Members) + .Where(p => p.Members.Any(m => m.AccountId == accountId)) + .FirstOrDefaultAsync(); + } + + private Guid GetCurrentUser() + { + // Implement based on your auth system + return Guid.Empty; + } +} + +public class TestFollowRequest +{ + public string TargetActorUri { get; set; } = string.Empty; +} + +public class TestLikeRequest +{ + public Guid PostId { get; set; } + public string TargetActorUri { get; set; } = string.Empty; +} + +public class TestAnnounceRequest +{ + public Guid PostId { get; set; } +} + +public class TestUndoRequest +{ + public string ActivityType { get; set; } = string.Empty; + public string ObjectUri { get; set; } = string.Empty; +} + +public class TestSignatureRequest +{ + public string PublicKey { get; set; } = string.Empty; + public string SigningString { get; set; } = string.Empty; + public string Signature { get; set; } = string.Empty; +} +``` + +## Testing with Helper Endpoints + +### 1. Test Follow +```bash +curl -X POST http://solar.local:5000/api/activitypub/test/follow \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -d '{ + "targetActorUri": "http://mastodon.local:3001/users/testuser" + }' +``` + +### 2. Test Like +```bash +curl -X POST http://solar.local:5000/api/activitypub/test/like \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -d '{ + "postId": "YOUR_POST_ID", + "targetActorUri": "http://mastodon.local:5000/activitypub/actors/mastodonuser" + }' +``` + +### 3. Check Status +```bash +curl http://solar.local:5000/api/activitypub/test/status \ + -H "Authorization: Bearer YOUR_TOKEN" +``` + +### 4. Get Recent Activities +```bash +curl "http://solar.local:5000/api/activitypub/test/activities?limit=10" \ + -H "Authorization: Bearer YOUR_TOKEN" +``` + +## Integration with Main Flow + +These helper endpoints can be used to: + +1. **Quickly test federation** without full UI integration +2. **Debug specific activity types** in isolation +3. **Verify HTTP signatures** are correct +4. **Test error handling** for various scenarios +5. **Monitor federation status** during development + +## Security Notes + +- All test endpoints require authentication +- Use only in development/staging environments +- Remove or disable in production +- Rate limiting recommended if exposing to public + +## Cleanup + +After testing, you can: + +1. Remove the test controller (optional) +2. Disable test endpoints +3. Clear test activities from database +4. Reset test relationships + +```sql +-- Clear test data +DELETE FROM fediverse_activities WHERE created_at < NOW() - INTERVAL '1 day'; +``` diff --git a/docs/ACTIVITYPUB_TESTING_INDEX.md b/docs/ACTIVITYPUB_TESTING_INDEX.md new file mode 100644 index 0000000..fec85d7 --- /dev/null +++ b/docs/ACTIVITYPUB_TESTING_INDEX.md @@ -0,0 +1,448 @@ +# ActivityPub Testing - Complete Guide + +This is the complete guide for testing ActivityPub federation in Solar Network. + +## ๐Ÿ“ File Overview + +| File | Purpose | When to Use | +|------|---------|--------------| +| `setup-activitypub-test.sh` | One-command setup of test environment | First time setup | +| `test-activitypub.sh` | Quick validation of basic functionality | After setup, before detailed tests | +| `ACTIVITYPUB_TESTING_QUICKSTART.md` | Quick start reference | Getting started quickly | +| `ACTIVITYPUB_TESTING_GUIDE.md` | Comprehensive testing scenarios | Full testing workflow | +| `ACTIVITYPUB_TESTING_QUICKREF.md` | Command and query reference | Daily testing | +| `ACTIVITYPUB_TESTING_HELPER_API.md` | Helper API for testing | Programmatic testing | +| `ACTIVITYPUB_TEST_RESULTS_TEMPLATE.md` | Track test results | During testing | +| `ACTIVITYPUB_IMPLEMENTATION.md` | Implementation details | Understanding the code | +| `ACTIVITYPUB_SUMMARY.md` | Feature summary | Reviewing what's implemented | + +## ๐Ÿš€ Quick Start (5 Minutes) + +### 1. Setup Test Environment + +```bash +./setup-activitypub-test.sh +``` + +This will: +- โœ… Configure `/etc/hosts` +- โœ… Start Mastodon via Docker +- โœ… Create test Mastodon account +- โœ… Apply database migrations + +### 2. Validate Setup + +```bash +./test-activitypub.sh +``` + +This checks: +- โœ… WebFinger endpoint +- โœ… Actor profile +- โœ… Public keys +- โœ… Database tables + +### 3. Start Solar Network + +```bash +cd DysonNetwork.Sphere +dotnet run +``` + +### 4. Test Federation + +1. Open http://mastodon.local:3001 +2. Search for `@solaruser@solar.local` +3. Click Follow +4. Create a post in Solar Network +5. Verify it appears in Mastodon + +## ๐Ÿ“– Recommended Reading Order + +### For First-Time Testing + +1. **Start Here**: `ACTIVITYPUB_TESTING_QUICKSTART.md` + - Overview of the setup + - Quick command reference + - Common commands + +2. **Then**: `ACTIVITYPUB_TESTING_GUIDE.md` + - Detailed test scenarios + - Step-by-step instructions + - Troubleshooting + +3. **Reference**: `ACTIVITYPUB_TESTING_QUICKREF.md` + - Command snippets + - Database queries + - Response codes + +### During Testing + +1. **Track Progress**: `ACTIVITYPUB_TEST_RESULTS_TEMPLATE.md` + - Checklists for each test + - Results tracking + - Issue logging + +2. **Helper API**: `ACTIVITYPUB_TESTING_HELPER_API.md` + - Manual testing endpoints + - Debugging tools + - Status monitoring + +### For Understanding + +1. **Implementation**: `ACTIVITYPUB_IMPLEMENTATION.md` + - Architecture details + - Service descriptions + - Data flow diagrams + +2. **Features**: `ACTIVITYPUB_SUMMARY.md` + - What's implemented + - Model relationships + - API endpoints + +## ๐Ÿ” Test Scenarios Summary + +### Basic Functionality (All instances must pass) + +- [ ] WebFinger discovery works +- [ ] Actor profile is valid JSON-LD +- [ ] Public key is present +- [ ] Outbox returns public posts +- [ ] Inbox accepts activities + +### Federation - Follow + +- [ ] Remote user can follow local user +- [ ] Local user can follow remote user +- [ ] Accept activity is sent/received +- [ ] Relationship state is correct +- [ ] Unfollow works correctly + +### Federation - Content + +- [ ] Local posts federate to remote instances +- [ ] Remote posts appear in local database +- [ ] Post content is preserved +- [ ] Timestamps are correct +- [ ] Attachments are handled +- [ ] Content warnings are respected + +### Federation - Interactions + +- [ ] Likes federate correctly +- [ ] Likes appear in both instances +- [ ] Replies federate correctly +- [ ] Reply threading works +- [ ] Boosts/Announces work +- [ ] Undo activities work + +### Security + +- [ ] HTTP signatures are verified +- [ ] Invalid signatures are rejected +- [ ] Keys are properly stored +- [ ] Private keys never exposed + +## ๐Ÿ› Common Issues & Solutions + +### Issue: "Failed to verify signature" + +**Causes**: +1. Signature header format is wrong +2. Public key doesn't match keyId +3. Date header is too old (>5 minutes) +4. Request body doesn't match digest + +**Solutions**: +1. Check signature format: `keyId="...",algorithm="...",headers="...",signature="..."` +2. Verify keyId in actor profile +3. Ensure Date header is recent +4. Check body is exactly what was signed + +### Issue: "Target actor or inbox not found" + +**Causes**: +1. Actor hasn't been fetched yet +2. Actor URL is incorrect +3. Remote instance is inaccessible + +**Solutions**: +1. Manually fetch actor first +2. Verify actor URL is correct +3. Test accessibility with curl + +### Issue: Activities not arriving + +**Causes**: +1. Network connectivity issue +2. Remote instance is down +3. Activity wasn't queued properly + +**Solutions**: +1. Check network connectivity +2. Verify remote instance is running +3. Check fediverse_activities table for status + +## ๐Ÿ“Š Monitoring During Tests + +### Check Logs + +```bash +# Solar Network ActivityPub logs +dotnet run --project DysonNetwork.Sphere 2>&1 | grep -i activitypub + +# Mastodon federation logs +docker compose -f docker-compose.mastodon-test.yml logs -f web | grep -i federation +``` + +### Monitor Database + +```bash +# 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 5;"' +``` + +### Test Network + +```bash +# Test connectivity between instances +curl -v http://mastodon.local:3001 +curl -v http://solar.local:5000 + +# Test with traceroute (if available) +traceroute mastodon.local +traceroute solar.local +``` + +## ๐ŸŽฏ Success Criteria + +### Minimal Viable Federation + +To consider ActivityPub implementation "working", all of these must pass: + +- โœ… WebFinger returns actor links +- โœ… Actor profile has all required fields +- โœ… Follow relationships work bidirectionally +- โœ… Public posts federate to followers +- โœ… Incoming posts are stored correctly +- โœ… HTTP signatures are verified +- โœ… Basic interaction types work (Like, Reply) + +### Full Production Ready + +For production, also need: + +- โœ… Activity queue with retry logic +- โœ… Rate limiting on outgoing deliveries +- โœ… Monitoring and alerting +- โœ… Admin interface for federation management +- โœ… Content filtering and moderation +- โœ… Instance blocking capabilities +- โœ… Performance optimization for high volume + +## ๐Ÿ” Security Checklist + +During testing, verify: + +- [ ] Private keys are never logged +- [ ] Private keys are never returned in API responses +- [ ] Only public keys are in actor profiles +- [ ] All incoming activities are signature-verified +- [ ] Invalid signatures are rejected with 401 +- [ ] TLS is used in production +- [ ] Host header is verified against request URL + +## ๐Ÿ“ˆ Performance Metrics + +Track these during testing: + +| Metric | Target | Actual | +|--------|--------|--------| +| WebFinger response time | <500ms | ___ ms | +| Actor fetch time | <1s | ___ ms | +| Signature verification time | <100ms | ___ ms | +| Activity processing time | <500ms | ___ ms | +| Outgoing delivery success rate | >95% | ___% | +| Outgoing delivery time | <5s | ___ ms | + +## ๐Ÿงช Testing Checklist + +### Self-Hosted Instance Tests + +**Setup**: +- [ ] Setup script completed +- [ ] Mast containers running +- [ ] Solar Network running +- [ ] /etc/hosts configured +- [ ] Database migrations applied + +**Basic Federation**: +- [ ] WebFinger works +- [ ] Actor profile valid +- [ ] Public key present +- [ ] Outbox accessible + +**Follow Flow**: +- [ ] Mastodon โ†’ Solar follow works +- [ ] Solar โ†’ Mastodon follow works +- [ ] Accept activity sent +- [ ] Relationship state correct + +**Content Flow**: +- [ ] Solar posts appear in Mastodon +- [ ] Mastodon posts appear in Solar +- [ ] Content preserved correctly +- [ ] Timestamps correct + +**Interactions**: +- [ ] Likes work both ways +- [ ] Replies work both ways +- [ ] Boosts work both ways +- [ ] Undo works + +**Security**: +- [ ] HTTP signatures verified +- [ ] Invalid signatures rejected +- [ ] Keys properly managed + +### Real Instance Tests + +**Discovery**: +- [ ] Domain publicly accessible +- [ ] WebFinger works from public internet +- [ ] Actor discoverable from public instances + +**Federation**: +- [ ] Posts federate to public instances +- [ ] Follows work with public instances +- [ ] Interactions work with public instances + +## ๐Ÿ“ Testing Notes + +### What Worked Well +1. _____________________ +2. _____________________ +3. _____________________ + +### What Needs Improvement +1. _____________________ +2. _____________________ +3. _____________________ + +### Bugs Found +| # | Description | Severity | Status | +|---|-------------|----------|--------| +| 1 | _____________________ | Low/Medium/High | โ˜ Open/โ˜ Fixed | +| 2 | _____________________ | Low/Medium/High | โ˜ Open/โ˜ Fixed | +| 3 | _____________________ | Low/Medium/High | โ˜ Open/โ˜ Fixed | + +## ๐ŸŽ“ Learning Resources + +### ActivityPub Specification +- [W3C ActivityPub Recommendation](https://www.w3.org/TR/activitypub/) +- [ActivityStreams 2.0](https://www.w3.org/TR/activitystreams-core/) +- [HTTP Signatures Draft](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures) + +### Implementation Guides +- [Mastodon Federation Guide](https://docs.joinmastodon.org/admin/federation/) +- [ActivityPub Testing Best Practices](https://blog.joinmastodon.org/2018/06/27/how-to-implement-a-basic-activitypub-server/) +- [Federation Testing Checklist](https://docs.joinmastodon.org/spec/activitypub/) + +### Tools +- [ActivityPub Playground](https://swicth.github.io/activity-pub-playground/) +- [FediTest](https://feditest.com/) +- [JSONPath Online Evaluator](https://jsonpath.com/) + +## ๐Ÿ”„ Next Steps After Testing + +### Phase 1: Fix Issues +- Address all bugs found during testing +- Improve error messages +- Add better logging + +### Phase 2: Enhance Features +- Implement activity queue +- Add retry logic +- Add rate limiting +- Implement instance blocking + +### Phase 3: Production Readiness +- Add monitoring and metrics +- Add admin interface +- Add content filtering +- Implement moderation tools + +### Phase 4: Additional Features +- Support more activity types +- Support media attachments +- Support polls +- Support custom emojis + +## ๐Ÿ“ž Getting Help + +If you encounter issues: + +1. **Check logs**: See the logs section above +2. **Review troubleshooting**: See `ACTIVITYPUB_TESTING_GUIDE.md` Part 5 +3. **Check database queries**: Use queries from `ACTIVITYPUB_TESTING_QUICKREF.md` +4. **Validate signatures**: Use helper API in `ACTIVITYPUB_TESTING_HELPER_API.md` + +## โœจ Quick Test Commands + +### All-in-One Test Sequence + +```bash +# 1. Setup +./setup-activitypub-test.sh + +# 2. Validate +./test-activitypub.sh + +# 3. Test WebFinger +curl "http://solar.local:5000/.well-known/webfinger?resource=acct:solaruser@solar.local" + +# 4. Test Actor +curl -H "Accept: application/activity+json" \ + http://solar.local:5000/activitypub/actors/solaruser + +# 5. Test Follow (from Mastodon UI) +# Open http://mastodon.local:3001 and follow @solaruser@solar.local + +# 6. Check database +psql -d dyson_network -c "SELECT * FROM fediverse_relationships;" + +# 7. Test Post (create in Solar Network UI) +# Should appear in http://mastodon.local:3001 + +# 8. Verify content +psql -d dyson_network -c "SELECT * FROM fediverse_contents;" + +# 9. Test Like (like from Mastodon UI) +# Should appear in fediverse_reactions table + +# 10. Check activities +psql -d dyson_network -c "SELECT type, status FROM fediverse_activities;" +``` + +## ๐ŸŽ‰ Conclusion + +You now have everything needed to test ActivityPub federation for Solar Network: + +- โœ… Self-hosted test environment (Mastodon) +- โœ… Setup automation (setup script) +- โœ… Quick validation (test script) +- โœ… Comprehensive testing guide +- โœ… Helper API for programmatic testing +- โœ… Quick reference for daily use +- โœ… Results template for tracking progress + +**Recommended workflow**: +1. Run `setup-activitypub-test.sh` +2. Run `test-activitypub.sh` for validation +3. Follow scenarios in `ACTIVITYPUB_TESTING_GUIDE.md` +4. Use `ACTIVITYPUB_TESTING_HELPER_API.md` for specific tests +5. Track results in `ACTIVITYPUB_TEST_RESULTS_TEMPLATE.md` +6. Reference `ACTIVITYPUB_TESTING_QUICKREF.md` for commands + +Good luck with your federation testing! ๐Ÿš€ diff --git a/docs/ACTIVITYPUB_TESTING_QUICKREF.md b/docs/ACTIVITYPUB_TESTING_QUICKREF.md new file mode 100644 index 0000000..da36adc --- /dev/null +++ b/docs/ACTIVITYPUB_TESTING_QUICKREF.md @@ -0,0 +1,356 @@ +# ActivityPub Testing Quick Reference + +## Quick Test Commands + +### 1. Test WebFinger +```bash +curl "http://solar.local:5000/.well-known/webfinger?resource=acct:username@solar.local" +``` + +### 2. Fetch Actor Profile +```bash +curl -H "Accept: application/activity+json" \ + http://solar.local:5000/activitypub/actors/username +``` + +### 3. Get Outbox +```bash +curl -H "Accept: application/activity+json" \ + http://solar.local:5000/activitypub/actors/username/outbox +``` + +### 4. Send Test Follow (from remote) +```bash +curl -X POST http://solar.local:5000/activitypub/actors/username/inbox \ + -H "Content-Type: application/activity+json" \ + -d '{ + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://mastodon.local:3001/follow-123", + "type": "Follow", + "actor": "https://mastodon.local:3001/users/remoteuser", + "object": "https://solar.local:5000/activitypub/actors/username" + }' +``` + +### 5. Send Test Create (post) +```bash +curl -X POST http://solar.local:5000/activitypub/actors/username/inbox \ + -H "Content-Type: application/activity+json" \ + -d '{ + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://mastodon.local:3001/post-123", + "type": "Create", + "actor": "https://mastodon.local:3001/users/remoteuser", + "object": { + "id": "https://mastodon.local:3001/objects/post-123", + "type": "Note", + "content": "Hello from Mastodon! @username@solar.local", + "attributedTo": "https://mastodon.local:3001/users/remoteuser", + "to": ["https://www.w3.org/ns/activitystreams#Public"] + } + }' +``` + +### 6. Send Test Like +```bash +curl -X POST http://solar.local:5000/activitypub/actors/username/inbox \ + -H "Content-Type: application/activity+json" \ + -d '{ + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://mastodon.local:3001/like-123", + "type": "Like", + "actor": "https://mastodon.local:3001/users/remoteuser", + "object": "https://solar.local:5000/activitypub/objects/post-id" + }' +``` + +## Database Queries + +### Check Actors +```sql +SELECT id, uri, username, display_name, instance_id +FROM fediverse_actors; +``` + +### Check Contents +```sql +SELECT id, uri, type, content, actor_id, created_at +FROM fediverse_contents +ORDER BY created_at DESC +LIMIT 20; +``` + +### Check Relationships +```sql +SELECT r.id, a1.uri as actor, a2.uri as target, r.state, r.is_following +FROM fediverse_relationships r +JOIN fediverse_actors a1 ON r.actor_id = a1.id +JOIN fediverse_actors a2 ON r.target_actor_id = a2.id; +``` + +### Check Activities +```sql +SELECT type, status, error_message, created_at +FROM fediverse_activities +ORDER BY created_at DESC +LIMIT 20; +``` + +### Check Reactions +```sql +SELECT r.type, c.uri as content_uri, a.uri as actor_uri +FROM fediverse_reactions r +JOIN fediverse_contents c ON r.content_id = c.id +JOIN fediverse_actors a ON r.actor_id = a.id; +``` + +## Check Keys in Publisher +```sql +SELECT id, name, meta +FROM publishers +WHERE meta IS NOT NULL; +``` + +## Docker Commands + +### Start Mastodon +```bash +docker-compose -f docker-compose.mastodon-test.yml up -d +``` + +### View Mastodon Logs +```bash +docker-compose -f docker-compose.mastodon-test.yml logs -f web +``` + +### Stop Mastodon +```bash +docker-compose -f docker-compose.mastodon-test.yml down +``` + +### Start GoToSocial +```bash +docker-compose -f docker-compose.gotosocial.yml up -d +``` + +## Solar Network Commands + +### Run Migrations +```bash +cd DysonNetwork.Sphere +dotnet ef database update +``` + +### Run with Debug Logging +```bash +dotnet run --project DysonNetwork.Sphere -- --logging:LogLevel:DysonNetwork.Sphere.ActivityPub=Trace +``` + +## Common Response Codes + +| Code | Meaning | +|------|---------| +| 200 | Success | +| 202 | Accepted (activity queued) | +| 401 | Unauthorized (invalid signature) | +| 404 | Not found (user/post doesn't exist) | +| 400 | Bad request (invalid activity) | + +## Activity Status Codes + +| Status | Code | Meaning | +|--------|------|---------| +| Pending | 0 | Activity waiting to be processed | +| Processing | 1 | Activity being processed | +| Completed | 2 | Activity successfully processed | +| Failed | 3 | Activity processing failed | + +## Relationship States + +| State | Code | Meaning | +|--------|------|---------| +| Pending | 0 | Follow request sent, waiting for Accept | +| Accepted | 1 | Follow accepted, relationship active | +| Rejected | 2 | Follow rejected | + +## Troubleshooting + +### "Failed to verify signature" + +**Check**: Signature header format +```bash +# Should be: +Signature: keyId="...",algorithm="rsa-sha256",headers="...",signature="..." +``` + +**Check**: Public key in actor profile +```bash +curl -H "Accept: application/activity+json" \ + http://solar.local:5000/activitypub/actors/username | jq '.publicKey' +``` + +### "Actor not found" + +**Check**: Actor exists in database +```bash +psql -d dyson_network -c \ + "SELECT * FROM fediverse_actors WHERE uri = '...';" +``` + +**Check**: Actor URL is accessible +```bash +curl -v http://remote-instance.com/users/username +``` + +### "Content already exists" + +This is normal behavior - the system is deduplicating. + +### "Target publisher not found" + +**Check**: Publisher exists +```bash +psql -d dyson_network -c \ + "SELECT * FROM publishers WHERE name = '...';" +``` + +## Quick Test Sequence + +### Full Federation Test + +```bash +# 1. Start both instances +docker-compose -f docker-compose.mastodon-test.yml up -d +dotnet run --project DysonNetwork.Sphere + +# 2. Test WebFinger +curl "http://solar.local:5000/.well-known/webfinger?resource=acct:solaruser@solar.local" + +# 3. Get Actor +curl -H "Accept: application/activity+json" \ + http://solar.local:5000/activitypub/actors/solaruser + +# 4. Send Follow from Mastodon +# (Do this in Mastodon UI or use curl above) + +# 5. Check database +psql -d dyson_network -c "SELECT * FROM fediverse_relationships;" + +# 6. Send Create (post) from Mastodon +# (Use curl command above) + +# 7. Check content +psql -d dyson_network -c "SELECT * FROM fediverse_contents;" + +# 8. Send Like from Mastodon +# (Use curl command above) + +# 9. Check reactions +psql -d dyson_network -c "SELECT * FROM fediverse_reactions;" + +# 10. Check activities +psql -d dyson_network -c "SELECT type, status FROM fediverse_activities;" +``` + +## Test URLs + +| Instance | Web | API | ActivityPub | +|----------|-----|-----|-----------| +| Solar Network | http://solar.local:5000 | http://solar.local:5000/api | http://solar.local:5000/activitypub | +| Mastodon | http://mastodon.local:3001 | http://mastodon.local:3001/api/v1 | http://mastodon.local:3001/inbox | + +## Environment Variables + +### Solar Network +```bash +export SOLAR_DOMAIN="solar.local" +export SOLAR_URL="http://solar.local:5000" +``` + +### Mastodon +```bash +export MASTODON_DOMAIN="mastodon.local" +export MASTODON_URL="http://mastodon.local:3001" +``` + +## Useful jq Commands + +### Extract Actor ID +```bash +curl ... | jq '.id' +``` + +### Extract Inbox URL +```bash +curl ... | jq '.inbox' +``` + +### Extract Public Key +```bash +curl ... | jq '.publicKey.publicKeyPem' +``` + +### Pretty Print Activity +```bash +curl ... | jq '.' +``` + +### Extract Activity Type +```bash +curl ... | jq '.type' +``` + +## Network Setup + +### /etc/hosts +``` +127.0.0.1 solar.local +127.0.0.1 mastodon.local +127.0.0.1 gotosocial.local +``` + +### Ports Used +- Solar Network: 5000 +- Mastodon: 3001 (web), 4000 (streaming) +- GoToSocial: 3002 +- PostgreSQL: 5432 +- Redis: 6379 +- Elasticsearch: 9200 + +## File Locations + +### Docker Compose Files +- `docker-compose.mastodon-test.yml` +- `docker-compose.gotosocial.yml` + +### Environment Files +- `.env.mastodon` + +### Data Volumes +- `./mastodon-data/` +- `./gotosocial-data/` + +## Clean Up Commands + +```bash +# Reset database +psql -d dyson_network <&1 | grep -i activitypub + +# Debug logging +dotnet run --project DysonNetwork.Sphere --logging:LogLevel:DysonNetwork.Sphere.ActivityPub=Trace +``` + +### Mastodon Logs +```bash +# All services +docker compose -f docker-compose.mastodon-test.yml logs -f + +# Web service only +docker compose -f docker-compose.mastodon-test.yml logs -f web + +# Filter for federation +docker compose -f docker-compose.mastodon-test.yml logs -f web | grep -i federation +``` + +## Stopping Everything + +```bash +# Stop Mastodon +docker compose -f docker-compose.mastodon-test.yml down + +# Stop with volume cleanup +docker compose -f docker-compose.mastodon-test.yml down -v + +# Restore /etc/hosts +sudo mv /etc/hosts.backup /etc/hosts + +# Remove test databases (optional) +psql -d dyson_network <&1 | grep -i error + +# Restart +# Ctrl+C in terminal and run again +``` + +### Activities not arriving + +```bash +# Check database +psql -d dyson_network -c "SELECT * FROM fediverse_activities WHERE status = 3;" + +# Check signature verification logs +dotnet run --project DysonNetwork.Sphere 2>&1 | grep -i "signature" + +# Verify actor keys +curl -H "Accept: application/activity+json" \ + http://solar.local:5000/activitypub/actors/solaruser | jq '.publicKey' +``` + +## Testing Checklist + +- [ ] Setup script completed successfully +- [ ] Mastodon is running and accessible +- [ ] Solar Network is running and accessible +- [ ] WebFinger returns correct data +- [ ] Actor profile includes public key +- [ ] Follow from Mastodon to Solar Network works +- [ ] Follow from Solar Network to Mastodon works +- [ ] Posts from Solar Network appear in Mastodon +- [ ] Posts from Mastodon appear in Solar Network database +- [ ] Likes federate correctly +- [ ] Replies federate correctly +- [ ] HTTP signatures are verified +- [ ] No errors in logs +- [ ] Database contains expected data + +## Next Steps + +1. **Test with a real instance**: + - Get a public domain or use ngrok + - Update `ActivityPub:Domain` in appsettings.json + - Test with mastodon.social or other public instances + +2. **Add more features**: + - Activity queue for async processing + - Retry logic for failed deliveries + - Metrics and monitoring + - Admin interface for federation management + +3. **Test with more instances**: + - Pleroma + - Pixelfed + - Lemmy + - PeerTube + +## Getting Help + +If something doesn't work: + +1. Check the logs (see Logs section above) +2. Review the troubleshooting section in [ACTIVITYPUB_TESTING_GUIDE.md](ACTIVITYPUB_TESTING_GUIDE.md) +3. Verify all prerequisites are installed +4. Check network connectivity between instances +5. Review the [ACTIVITYPUB_IMPLEMENTATION.md](ACTIVITYPUB_IMPLEMENTATION.md) for architecture details + +## Useful URLs + +### Test Instances +- Mastodon: http://mastodon.local:3001 +- Solar Network: http://solar.local:5000 + +### Documentation +- ActivityPub W3C Spec: https://www.w3.org/TR/activitypub/ +- Mastodon Federation Docs: https://docs.joinmastodon.org/admin/federation/ +- ActivityPub Playground: https://swicth.github.io/activity-pub-playground/ + +### Tools +- jq: JSON processor (https://stedolan.github.io/jq/) +- httpie: HTTP client (https://httpie.io/) +- Docker Compose: (https://docs.docker.com/compose/) diff --git a/docs/ACTIVITYPUB_TESTING_README.md b/docs/ACTIVITYPUB_TESTING_README.md new file mode 100644 index 0000000..d6804c6 --- /dev/null +++ b/docs/ACTIVITYPUB_TESTING_README.md @@ -0,0 +1,275 @@ +# ActivityPub Testing Guide + +Complete guide for testing ActivityPub federation in Solar Network. + +## ๐Ÿ“š Documentation Files + +| File | Description | Size | +|------|-------------|-------| +| `ACTIVITYPUB_TESTING_INDEX.md` | **START HERE** - Master guide with overview | 12K | +| `ACTIVITYPUB_TESTING_QUICKSTART.md` | Quick reference for common tasks | 7K | +| `ACTIVITYPUB_TESTING_GUIDE.md` | Comprehensive testing scenarios (10 parts) | 19K | +| `ACTIVITYPUB_TESTING_QUICKREF.md` | Command and query reference | 8K | +| `ACTIVITYPUB_TESTING_HELPER_API.md` | Helper API for programmatic testing | 12K | +| `ACTIVITYPUB_TESTING_RESULTS_TEMPLATE.md` | Template to track test results | 10K | + +## ๐Ÿš€ Quick Start + +### Option A: One-Command Setup (Recommended) + +```bash +# 1. Run setup script +./setup-activitypub-test.sh + +# 2. Run validation +./test-activitypub.sh + +# 3. Start Solar Network +cd DysonNetwork.Sphere +dotnet run +``` + +### Option B: Manual Setup + +1. **Read**: `ACTIVITYPUB_TESTING_QUICKSTART.md` +2. **Configure**: Copy `.env.testing.example` to `.env` and adjust +3. **Follow**: Step-by-step in `ACTIVITYPUB_TESTING_GUIDE.md` + +## ๐ŸŽฏ What You Can Test + +### With Self-Hosted Instance +- โœ… WebFinger discovery +- โœ… Actor profile retrieval +- โœ… Follow relationships (bidirectional) +- โœ… Post federation (Solar โ†’ Mastodon) +- โœ… Content reception (Mastodon โ†’ Solar) +- โœ… Like interactions +- โœ… Reply threading +- โœ… HTTP signature verification +- โœ… Content deletion + +### With Real Instance +- โœ… Public domain setup (via ngrok or VPS) +- โœ… Federation with public instances (mastodon.social, etc.) +- โœ… Real-world compatibility testing +- โœ… Performance under real load + +## ๐Ÿ“‹ Testing Workflow + +### Day 1: Basic Functionality +- Setup test environment +- Test WebFinger and Actor endpoints +- Verify HTTP signatures +- Test basic follow/unfollow + +### Day 2: Content Federation +- Test post creation and delivery +- Test content reception +- Test media attachments +- Test content warnings + +### Day 3: Interactions +- Test likes (both directions) +- Test replies and threading +- Test boosts/announces +- Test undo activities + +### Day 4: Real Instance +- Set up public domain +- Test with mastodon.social +- Test with other instances +- Verify cross-instance compatibility + +### Day 5: Edge Cases +- Test error handling +- Test failed deliveries +- Test invalid signatures +- Test malformed activities + +## ๐Ÿ› ๏ธ Setup Scripts + +| Script | Purpose | +|--------|---------| +| `setup-activitypub-test.sh` | One-command setup of Mastodon + Solar Network | +| `test-activitypub.sh` | Quick validation of core functionality | + +Both scripts are executable (`chmod +x`). + +## ๐Ÿ”ง Configuration + +### Required Tools +- โœ… Docker (for Mastodon) +- โœ… .NET 10 SDK (for Solar Network) +- โœ… PostgreSQL client (psql) +- โœ… curl (for API testing) + +### Quick Setup +```bash +# 1. Install dependencies (Ubuntu/Debian) +sudo apt-get install docker.io docker-compose postgresql-client curl jq + +# 2. Run setup +./setup-activitypub-test.sh + +# 3. Validate +./test-activitypub.sh +``` + +## ๐Ÿ“Š Progress Tracking + +Use the template to track your testing progress: + +```bash +# Copy the template +cp ACTIVITYPUB_TESTING_RESULTS_TEMPLATE.md my-test-results.md + +# Edit as you test +nano my-test-results.md +``` + +## ๐Ÿ› Troubleshooting + +### Quick Fixes + +**Mastodon won't start**: +```bash +# Check logs +docker compose -f docker-compose.mastodon-test.yml logs -f + +# Restart containers +docker compose -f docker-compose.mastodon-test.yml restart +``` + +**Can't reach Solar Network**: +```bash +# Check if running +curl http://solar.local:5000 + +# Check /etc/hosts +cat /etc/hosts | grep solar.local +``` + +**Activities not arriving**: +```bash +# Check database +psql -d dyson_network -c "SELECT * FROM fediverse_activities;" + +# Check logs +dotnet run --project DysonNetwork.Sphere | grep -i activitypub +``` + +For detailed troubleshooting, see `ACTIVITYPUB_TESTING_GUIDE.md` Part 5. + +## ๐Ÿ“– Learning Path + +### For Developers +1. Read `ACTIVITYPUB_IMPLEMENTATION.md` to understand the architecture +2. Read `ACTIVITYPUB_SUMMARY.md` to see what's implemented +3. Follow test scenarios in `ACTIVITYPUB_TESTING_GUIDE.md` +4. Use helper API in `ACTIVITYPUB_TESTING_HELPER_API.md` for testing + +### For Testers +1. Start with `ACTIVITYPUB_TESTING_QUICKSTART.md` +2. Use command reference in `ACTIVITYPUB_TESTING_QUICKREF.md` +3. Track results with `ACTIVITYPUB_TESTING_RESULTS_TEMPLATE.md` +4. Report issues with details from logs + +## ๐ŸŽ“ Success Criteria + +### Minimum Viable +- WebFinger works +- Actor profile valid +- Follow relationships work +- Posts federate correctly +- HTTP signatures verified + +### Production Ready +- Activity queue with retry +- Rate limiting +- Monitoring/alerting +- Admin interface +- Instance blocking +- Content moderation + +## ๐Ÿšจ Common Pitfalls + +### Don't Forget +- โœ… Update `/etc/hosts` with both instances +- โœ… Run migrations before testing +- โœ… Check both instances are accessible +- โœ… Verify PostgreSQL is running +- โœ… Check logs when something fails + +### Watch Out For +- โŒ Using `localhost` instead of `solar.local` +- โŒ Forgetting to restart after config changes +- โŒ Not waiting for Mastodon to start (2-5 minutes) +- โŒ Ignoring CORS errors in browser testing +- โŒ Testing with deleted/invisible posts + +## ๐Ÿ“š Additional Resources + +### Official Specs +- [ActivityPub W3C](https://www.w3.org/TR/activitypub/) +- [ActivityStreams](https://www.w3.org/TR/activitystreams-core/) +- [HTTP Signatures](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures) + +### Community Guides +- [Mastodon Federation](https://docs.joinmastodon.org/admin/federation/) +- [Federation Testing](https://docs.joinmastodon.org/spec/activitypub/) + +### Tools +- [ActivityPub Playground](https://swicth.github.io/activity-pub-playground/) +- [FediTest](https://feditest.com/) + +## ๐Ÿ†˜ Support + +If you encounter issues: + +1. Check logs (both Solar Network and Mastodon) +2. Review troubleshooting section in the guide +3. Validate against success criteria +4. Check database state with queries +5. Review implementation docs + +## โœจ Next Steps + +After testing with self-hosted instance: + +1. Get a public domain or use ngrok +2. Update `ActivityPub:Domain` in appsettings.json +3. Test with public Mastodon instances +4. Add more ActivityPub features (queue, retry, etc.) +5. Implement admin interface +6. Add monitoring and metrics + +## ๐Ÿ“ž File Reference + +All files are in the root of the DysonNetwork project: + +``` +DysonNetwork/ +โ”œโ”€โ”€ ACTIVITYPUB_TESTING_INDEX.md # Start here! +โ”œโ”€โ”€ ACTIVITYPUB_TESTING_QUICKSTART.md # Quick reference +โ”œโ”€โ”€ ACTIVITYPUB_TESTING_GUIDE.md # Full guide +โ”œโ”€โ”€ ACTIVITYPUB_TESTING_QUICKREF.md # Commands +โ”œโ”€โ”€ ACTIVITYPUB_TESTING_HELPER_API.md # Test API +โ”œโ”€โ”€ ACTIVITYPUB_TESTING_RESULTS_TEMPLATE.md +โ”œโ”€โ”€ setup-activitypub-test.sh # Setup script +โ”œโ”€โ”€ test-activitypub.sh # Test script +โ””โ”€โ”€ .env.testing.example # Config template +``` + +**Documentation files** (for reference): +``` +DysonNetwork/ +โ”œโ”€โ”€ ACTIVITYPUB_IMPLEMENTATION.md # How it's implemented +โ”œโ”€โ”€ ACTIVITYPUB_SUMMARY.md # Feature summary +โ””โ”€โ”€ ACTIVITYPUB_PLAN.md # Original plan +``` + +--- + +**Start here**: `ACTIVITYPUB_TESTING_INDEX.md` + +**Good luck with your testing!** ๐Ÿš€ diff --git a/docs/ACTIVITYPUB_TEST_RESULTS_TEMPLATE.md b/docs/ACTIVITYPUB_TEST_RESULTS_TEMPLATE.md new file mode 100644 index 0000000..e1ea87b --- /dev/null +++ b/docs/ACTIVITYPUB_TEST_RESULTS_TEMPLATE.md @@ -0,0 +1,282 @@ +# ActivityPub Testing Results Template + +Use this template to track your testing progress. + +## Test Environment + +**Date**: ________________ + +**Test Configuration**: +- Solar Network URL: `http://solar.local:5000` +- Mastodon URL: `http://mastodon.local:3001` +- Database: `dyson_network` + +**Solar Network User**: +- Username: `_______________` +- Publisher ID: `_______________` + +**Mastodon User**: +- Username: `testuser@mastodon.local` +- Password: `TestPassword123!` + +--- + +## Test Results + +### โœ… Part 1: Infrastructure Setup + +| Test | Status | Notes | +|------|--------|-------| +| Setup script ran successfully | โ˜ โ˜‘ | | +| /etc/hosts updated | โ˜ โ˜‘ | | +| Docker containers started | โ˜ โ˜‘ | | +| Mastodon web accessible | โ˜ โ˜‘ | | +| Mastodon admin account created | โ˜ โ˜‘ | | +| Database migrations applied | โ˜ โ˜‘ | | +| Solar Network started | โ˜ โ˜‘ | | + +### โœ… Part 2: WebFinger & Actor Discovery + +| Test | Status | Expected | Actual | +|------|--------|---------|--------| +| WebFinger for Solar Network user | โ˜ โ˜‘ | Returns subject + links | _______________ | +| Actor profile JSON is valid | โ˜ โ˜‘ | Has id, type, inbox, outbox | _______________ | +| Public key present in actor | โ˜ โ˜‘ | publicKey.publicKeyPem exists | _______________ | +| Outbox returns public posts | โ˜ โ˜‘ | OrderedCollection with items | _______________ | +| Outbox totalItems count | โ˜ โ˜‘ | Matches public posts | _______________ | + +### โœ… Part 3: Follow Relationships + +| Test | Status | Expected Result | Actual Result | +|------|--------|----------------|---------------| +| Mastodon follows Solar Network user | โ˜ โ˜‘ | Relationship created in DB | _______________ | +| Accept sent to Mastodon | โ˜ โ˜‘ | Mastodon receives Accept | _______________ | +| Solar Network follows Mastodon user | โ˜ โ˜‘ | Relationship created | _______________ | +| Follow appears in Mastodon UI | โ˜ โ˜‘ | Mastodon shows "Following" | _______________ | +| Follow appears in Solar Network DB | โ˜ โ˜‘ | is_following = true | _______________ | +| Follow state is Accepted | โ˜ โ˜‘ | state = 1 (Accepted) | _______________ | +| Unfollow works correctly | โ˜ โ˜‘ | Relationship deleted/updated | _______________ | + +### โœ… Part 4: Content Federation (Create) + +| Test | Status | Expected Result | Actual Result | +|------|--------|----------------|---------------| +| Post created in Solar Network | โ˜ โ˜‘ | Post in sn_posts table | _______________ | +| Activity sent to Mastodon | โ˜ โ˜‘ | Logged as successful | _______________ | +| Post appears in Mastodon timeline | โ˜ โ˜‘ | Visible in federated timeline | _______________ | +| Post content matches | โ˜ โ˜‘ | Same text/HTML | _______________ | +| Post author is correct | โ˜ โ˜‘ | Shows Solar Network user | _______________ | +| Post timestamp is correct | โ˜ โ˜‘ | Same published time | _______________ | +| Multiple posts federate | โ˜ โ˜‘ | All posts appear | _______________ | + +### โœ… Part 5: Content Reception (Incoming Create) + +| Test | Status | Expected Result | Actual Result | +|------|--------|----------------|---------------| +| Create activity received | โ˜ โ˜‘ | Activity logged in DB | _______________ | +| Content stored in fediverse_contents | โ˜ โ˜‘ | Record with correct type | _______________ | +| Content not duplicated | โ˜ โ˜‘ | Only one entry per URI | _______________ | +| Actor created/retrieved | โ˜ โ˜‘ | Actor in fediverse_actors | _______________ | +| Instance created/retrieved | โ˜ โ˜‘ | Instance in fediverse_instances | _______________ | +| Content HTML preserved | โ˜ โ˜‘ | contentHtml field populated | _______________ | + +### โœ… Part 6: Reaction Federation (Like) + +| Test | Status | Expected Result | Actual Result | +|------|--------|----------------|---------------| +| Like from Mastodon to Solar post | โ˜ โ˜‘ | Like activity received | _______________ | +| Reaction stored in fediverse_reactions | โ˜ โ˜‘ | Record with type = 0 (Like) | _______________ | +| Like count incremented | โ˜ โ˜‘ | like_count increased | _______________ | +| Like appears in UI | โ˜ โ˜‘ | Visible on Solar Network | _______________ | +| Like appears in Mastodon | โ˜ โ˜‘ | Visible on Mastodon | _______________ | +| Unlike works correctly | โ˜ โ˜‘ | Like removed | _______________ | + +### โœ… Part 7: Reply Federation + +| Test | Status | Expected Result | Actual Result | +|------|--------|----------------|---------------| +| Reply from Mastodon to Solar post | โ˜ โ˜‘ | Create activity with inReplyTo | _______________ | +| Reply stored with parent reference | โ˜ โ˜‘ | in_reply_to field set | _______________ | +| Reply appears in Solar Network | โ˜ โ˜‘ | Visible as comment | _______________ | +| Reply shows parent context | โ˜ โ˜‘ | Links to original post | _______________ | + +### โœ… Part 8: Content Deletion + +| Test | Status | Expected Result | Actual Result | +|------|--------|----------------|---------------| +| Delete from Mastodon | โ˜ โ˜‘ | Delete activity received | _______________ | +| Content soft-deleted | โ˜ โ˜‘ | deleted_at timestamp set | _______________ | +| Content no longer visible | โ˜ โ˜‘ | Hidden from timelines | _______________ | + +### โœ… Part 9: HTTP Signature Verification + +| Test | Status | Expected Result | Actual Result | +|------|--------|----------------|---------------| +| Valid signature accepted | โ˜ โ˜‘ | Activity processed | _______________ | +| Invalid signature rejected | โ˜ โ˜‘ | 401 Unauthorized | _______________ | +| Missing signature rejected | โ˜ โ˜‘ | 401 Unauthorized | _______________ | +| Signature format correct | โ˜ โ˜‘ | keyId, algorithm, headers, signature | _______________ | +| Signing string correct | โ˜ โ˜‘ | Matches HTTP-Signatures draft | _______________ | + +### โœ… Part 10: Error Handling + +| Test | Status | Expected Result | Actual Result | +|------|--------|----------------|---------------| +| Invalid activity type rejected | โ˜ โ˜‘ | 400 Bad Request | _______________ | +| Malformed JSON rejected | โ˜ โ˜‘ | 400 Bad Request | _______________ | +| Non-existent actor rejected | โ˜ โ˜‘ | 404 Not Found | _______________ | +| Errors logged correctly | โ˜ โ˜‘ | error_message populated | _______________ | +| Activity status = Failed | โ˜ โ˜‘ | status = 3 | _______________ | + +--- + +## Database State After Tests + +### Actors Table +```sql +SELECT COUNT(*) as total_actors, + SUM(CASE WHEN is_local_actor THEN 1 ELSE 0 END) as local, + SUM(CASE WHEN NOT is_local_actor THEN 1 ELSE 0 END) as remote +FROM fediverse_relationships; +``` +- Total Actors: _______________ +- Local Actors: _______________ +- Remote Actors: _______________ + +### Contents Table +```sql +SELECT COUNT(*) as total_contents, + AVG(LENGTH(content)) as avg_content_length +FROM fediverse_contents WHERE deleted_at IS NULL; +``` +- Total Contents: _______________ +- Avg Content Length: _______________ + +### Activities Table +```sql +SELECT type, status, COUNT(*) +FROM fediverse_activities +GROUP BY type, status +ORDER BY type, status; +``` +- Activities by Type/Status: + - Create: Pending ___, Completed ____, Failed ___ + - Follow: Pending ___, Completed ____, Failed ___ + - Like: Pending ___, Completed ____, Failed ___ + - Accept: Pending ___, Completed ____, Failed ___ + +### Relationships Table +```sql +SELECT state, COUNT(*) as count +FROM fediverse_relationships +GROUP BY state; +``` +- Pending: _______________ +- Accepted: _______________ +- Rejected: _______________ + +--- + +## Logs Analysis + +### Solar Network Errors Found: +1. _______________ +2. _______________ +3. _______________ + +### Mastodon Errors Found: +1. _______________ +2. _______________ +3. _______________ + +### Warnings Found: +1. _______________ +2. _______________ +3. _______________ + +--- + +## Issues & Bugs Found + +| # | Severity | Description | Status | +|---|----------|-------------|--------| +| 1 | โ˜ Low/Medium/High/Critical | _____________________ | โ˜ Open/โ˜ Fixed | +| 2 | โ˜ Low/Medium/High/Critical | _____________________ | โ˜ Open/โ˜ Fixed | +| 3 | โ˜ Low/Medium/High/Critical | _____________________ | โ˜ Open/โ˜ Fixed | +| 4 | โ˜ Low/Medium/High/Critical | _____________________ | โ˜ Open/โ˜ Fixed | + +--- + +## Performance Notes + +| Metric | Value | Notes | +|--------|-------|-------| +| Average activity processing time | __________ ms | | +| Average HTTP signature verification time | __________ ms | | +| Outgoing delivery success rate | __________% | | +| Average WebFinger response time | __________ ms | | +| Database query performance | __________ | | + +--- + +## Compatibility Notes + +| Instance | Version | Works | Notes | +|----------|---------|--------|-------| +| Mastodon (self-hosted) | latest | โ˜ โ˜‘ | | +| Mastodon.social | ~4.0 | โ˜ โ˜‘ | | +| Pleroma | ~2.5 | โ˜ โ˜‘ | | +| GoToSocial | ~0.15 | โ˜ โ˜‘ | | + +--- + +## Recommendations + +### What Worked Well: +1. _____________________ +2. _____________________ +3. _____________________ + +### What Needs Improvement: +1. _____________________ +2. _____________________ +3. _____________________ + +### Features to Add: +1. _____________________ +2. _____________________ +3. _____________________ + +--- + +## Next Testing Phase + +- [ ] Test with public Mastodon instance +- [ ] Test with Pleroma instance +- [ ] Test media attachment federation +- [ ] Test with high-volume posts +- [ ] Test concurrent activity processing +- [ ] Test with different visibility levels +- [ ] Test with long posts (>500 chars) +- [ ] Test with special characters/emojis + +--- + +## Sign-off + +**Tested By**: _____________________ + +**Test Date**: _____________________ + +**Overall Result**: โ˜ Pass / โ˜ Fail + +**Ready for Production**: โ˜ Yes / โ˜ No + +**Notes**: ___________________________________________________________________________ + +__________________________________________________________________________ + +__________________________________________________________________________ + +__________________________________________________________________________ + diff --git a/setup-activitypub-test.sh b/setup-activitypub-test.sh new file mode 100755 index 0000000..d4b16f4 --- /dev/null +++ b/setup-activitypub-test.sh @@ -0,0 +1,303 @@ +#!/bin/bash +# Quick setup script for ActivityPub testing + +set -e + +echo "======================================" +echo "ActivityPub Testing Setup" +echo "======================================" +echo "" + +# Colors +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +# Configuration +SOLAR_DOMAIN="solar.local" +SOLAR_PORT="5000" +MASTODON_DOMAIN="mastodon.local" +MASTODON_PORT="3001" + +echo -e "${YELLOW}1. Checking prerequisites...${NC}" + +# Check if Docker is installed +if ! command -v docker &> /dev/null; then + echo -e "${RED}Error: Docker is not installed${NC}" + exit 1 +fi + +# Check if docker-compose is installed +if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then + echo -e "${RED}Error: docker-compose is not installed${NC}" + exit 1 +fi + +# Check if psql is installed +if ! command -v psql &> /dev/null; then + echo -e "${RED}Error: PostgreSQL client (psql) is not installed${NC}" + exit 1 +fi + +echo -e "${GREEN}โœ“ All prerequisites found${NC}" +echo "" + +echo -e "${YELLOW}2. Updating /etc/hosts...${NC}" + +# Backup hosts file +sudo cp /etc/hosts /etc/hosts.backup + +# Check if entries already exist +if ! grep -q "$SOLAR_DOMAIN" /etc/hosts; then + echo "127.0.0.1 $SOLAR_DOMAIN" | sudo tee -a /etc/hosts > /dev/null + echo -e "${GREEN}โœ“ Added $SOLAR_DOMAIN to hosts${NC}" +else + echo -e "${YELLOW}โŠ™ $SOLAR_DOMAIN already in hosts${NC}" +fi + +if ! grep -q "$MASTODON_DOMAIN" /etc/hosts; then + echo "127.0.0.1 $MASTODON_DOMAIN" | sudo tee -a /etc/hosts > /dev/null + echo -e "${GREEN}โœ“ Added $MASTODON_DOMAIN to hosts${NC}" +else + echo -e "${YELLOW}โŠ™ $MASTODON_DOMAIN already in hosts${NC}" +fi + +echo "" + +echo -e "${YELLOW}3. Generating secrets for Mastodon...${NC}" + +SECRET_KEY_BASE=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-32) +OTP_SECRET=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-32) + +cat > .env.mastodon < docker-compose.mastodon-test.yml </dev/null || echo "$WEBFINGER_RESPONSE" | head -20 +else + echo -e "${RED}โœ— WebFinger failed${NC}" + echo "Response: $WEBFINGER_RESPONSE" +fi +echo "" + +# Test 2: Actor Profile +echo -e "${YELLOW}[2/7] Testing Actor Profile...${NC}" +ACTOR_RESPONSE=$(curl -s -H "Accept: application/activity+json" "http://solar.local:5000/activitypub/actors/$TEST_USER") + +if echo "$ACTOR_RESPONSE" | grep -q "inbox" && echo "$ACTOR_RESPONSE" | grep -q "outbox"; then + echo -e "${GREEN}โœ“ Actor profile is valid${NC}" + echo "$ACTOR_RESPONSE" | jq '.' 2>/dev/null || echo "$ACTOR_RESPONSE" | head -30 +else + echo -e "${RED}โœ— Actor profile is invalid${NC}" + echo "Response: $ACTOR_RESPONSE" +fi +echo "" + +# Test 3: Outbox +echo -e "${YELLOW}[3/7] Testing Outbox...${NC}" +OUTBOX_RESPONSE=$(curl -s -H "Accept: application/activity+json" "http://solar.local:5000/activitypub/actors/$TEST_USER/outbox") + +if echo "$OUTBOX_RESPONSE" | grep -q "OrderedCollection"; then + TOTAL_ITEMS=$(echo "$OUTBOX_RESPONSE" | jq '.totalItems' 2>/dev/null || echo "N/A") + echo -e "${GREEN}โœ“ Outbox is working ($TOTAL_ITEMS items)${NC}" +else + echo -e "${RED}โœ— Outbox failed${NC}" + echo "Response: $OUTBOX_RESPONSE" +fi +echo "" + +# Test 4: Public Key Presence +echo -e "${YELLOW}[4/7] Checking Public Key in Actor...${NC}" +PUBLIC_KEY=$(echo "$ACTOR_RESPONSE" | jq -r '.publicKey.publicKeyPem' 2>/dev/null) + +if [ -n "$PUBLIC_KEY" ] && [ "$PUBLIC_KEY" != "null" ]; then + KEY_TYPE=$(echo "$PUBLIC_KEY" | head -1) + echo -e "${GREEN}โœ“ Public key present ($KEY_TYPE)${NC}" +else + echo -e "${RED}โœ— No public key found${NC}" +fi +echo "" + +# Test 5: Database - Actors +echo -e "${YELLOW}[5/7] Checking Database - Actors...${NC}" +ACTORS_COUNT=$(psql -d dyson_network -t -c "SELECT COUNT(*) FROM fediverse_actors;" 2>/dev/null || echo "0") + +if [ "$ACTORS_COUNT" -gt 0 ]; then + echo -e "${GREEN}โœ“ Found $ACTORS_COUNT actors in database${NC}" + psql -d dyson_network -c "SELECT uri, username FROM fediverse_actors LIMIT 5;" 2>/dev/null || true +else + echo -e "${YELLOW}No actors in database yet${NC}" +fi +echo "" + +# Test 6: Database - Activities +echo -e "${YELLOW}[6/7] Checking Database - Activities...${NC}" +ACTIVITIES_COUNT=$(psql -d dyson_network -t -c "SELECT COUNT(*) FROM fediverse_activities;" 2>/dev/null || echo "0") + +if [ "$ACTIVITIES_COUNT" -gt 0 ]; then + echo -e "${GREEN}โœ“ Found $ACTIVITIES_COUNT activities in database${NC}" + echo "" + echo "Recent activities:" + psql -d dyson_network -c "SELECT type, status, created_at FROM fediverse_activities ORDER BY created_at DESC LIMIT 5;" 2>/dev/null || true +else + echo -e "${YELLOW}No activities in database yet (expected before federation tests)${NC}" +fi +echo "" + +# Test 7: Publisher Keys +echo -e "${YELLOW}[7/7] Checking Publisher Keys...${NC}" +KEYS_COUNT=$(psql -d dyson_network -t -c "SELECT COUNT(*) FROM publishers WHERE meta IS NOT NULL;" 2>/dev/null || echo "0") + +if [ "$KEYS_COUNT" -gt 0 ]; then + echo -e "${GREEN}โœ“ Found $KEYS_COUNT publishers with keys${NC}" + psql -d dyson_network -c "SELECT id, name FROM publishers WHERE meta IS NOT NULL LIMIT 5;" 2>/dev/null || true +else + echo -e "${YELLOW}No publishers with keys yet (keys will be generated on first federation activity)${NC}" +fi +echo "" + +# Summary +echo -e "${BLUE}================================" +echo "Test Summary" +echo "================================${NC}" +echo "" +echo "Solar Network: $SOLAR_URL" +echo "Mastodon: $MASTODON_URL" +echo "" +echo "All basic validation tests completed!" +echo "" +echo "Next steps:" +echo " 1. Test federation between Solar Network and Mastodon" +echo " 2. Create a post in Solar Network" +echo " 3. Verify it appears in Mastodon" +echo " 4. Follow users across instances" +echo " 5. Test likes and replies" +echo "" +echo "For detailed testing scenarios, see: ACTIVITYPUB_TESTING_GUIDE.md" +echo "For quick command reference, see: ACTIVITYPUB_TESTING_QUICKREF.md" +echo "" + +# Health checks +echo -e "${BLUE}Health Status:${NC}" + +# Check Solar Network +if curl -s -f "http://solar.local:5000" > /dev/null; then + echo -e " ${GREEN}โœ“ Solar Network is accessible${NC}" +else + echo -e " ${RED}โœ— Solar Network is not accessible${NC}" +fi + +# Check Mastodon +if curl -s -f "http://mastodon.local:3001" > /dev/null; then + echo -e " ${GREEN}โœ“ Mastodon is accessible${NC}" +else + echo -e " ${RED}โœ— Mastodon is not accessible${NC}" +fi + +# Check PostgreSQL +if psql -d dyson_network -c "SELECT 1;" > /dev/null 2>&1; then + echo -e " ${GREEN}โœ“ Database is accessible${NC}" +else + echo -e " ${RED}โœ— Database is not accessible${NC}" +fi + +echo ""