🔨 Add some tests utilities of activity pub
This commit is contained in:
43
.env.testing.example
Normal file
43
.env.testing.example
Normal file
@@ -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
|
||||
820
docs/ACTIVITYPUB_TESTING_GUIDE.md
Normal file
820
docs/ACTIVITYPUB_TESTING_GUIDE.md
Normal file
@@ -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/)
|
||||
506
docs/ACTIVITYPUB_TESTING_HELPER_API.md
Normal file
506
docs/ACTIVITYPUB_TESTING_HELPER_API.md
Normal file
@@ -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<ActivityPubTestController> logger
|
||||
) : ControllerBase
|
||||
{
|
||||
[HttpPost("follow")]
|
||||
public async Task<ActionResult> 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<ActionResult> 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<ActionResult> 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<ActionResult> 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<ActionResult> 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<ActionResult> 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<ActionResult> 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<SnPublisher?> 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';
|
||||
```
|
||||
448
docs/ACTIVITYPUB_TESTING_INDEX.md
Normal file
448
docs/ACTIVITYPUB_TESTING_INDEX.md
Normal file
@@ -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! 🚀
|
||||
356
docs/ACTIVITYPUB_TESTING_QUICKREF.md
Normal file
356
docs/ACTIVITYPUB_TESTING_QUICKREF.md
Normal file
@@ -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 <<EOF
|
||||
TRUNCATE fediverse_activities CASCADE;
|
||||
TRUNCATE fediverse_relationships CASCADE;
|
||||
TRUNCATE fediverse_reactions CASCADE;
|
||||
TRUNCATE fediverse_contents CASCADE;
|
||||
TRUNCATE fediverse_actors CASCADE;
|
||||
TRUNCATE fediverse_instances CASCADE;
|
||||
UPDATE publishers SET meta = NULL WHERE meta IS NOT NULL;
|
||||
EOF
|
||||
|
||||
# Reset everything
|
||||
docker-compose -f docker-compose.mastodon-test.yml down -v
|
||||
docker-compose -f docker-compose.gotosocial.yml down -v
|
||||
psql -d dyson_network <<EOF
|
||||
DROP SCHEMA public CASCADE;
|
||||
CREATE SCHEMA public;
|
||||
EOF
|
||||
dotnet ef database drop
|
||||
dotnet ef database update
|
||||
```
|
||||
289
docs/ACTIVITYPUB_TESTING_QUICKSTART.md
Normal file
289
docs/ACTIVITYPUB_TESTING_QUICKSTART.md
Normal file
@@ -0,0 +1,289 @@
|
||||
# ActivityPub Testing - Quick Start
|
||||
|
||||
This directory contains everything you need to test ActivityPub federation for Solar Network.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Run the Setup Script
|
||||
|
||||
```bash
|
||||
./setup-activitypub-test.sh
|
||||
```
|
||||
|
||||
This will:
|
||||
- ✅ Check prerequisites (Docker, PostgreSQL)
|
||||
- ✅ Update `/etc/hosts` with test domains
|
||||
- ✅ Generate Mastodon environment file
|
||||
- ✅ Create Docker Compose file
|
||||
- ✅ Start Mastodon containers
|
||||
- ✅ Create test Mastodon account
|
||||
- ✅ Apply Solar Network migrations
|
||||
|
||||
### 2. Start Solar Network
|
||||
|
||||
```bash
|
||||
cd DysonNetwork.Sphere
|
||||
dotnet run
|
||||
```
|
||||
|
||||
### 3. Test Federation
|
||||
|
||||
Follow the scenarios in [ACTIVITYPUB_TESTING_GUIDE.md](ACTIVITYPUB_TESTING_GUIDE.md)
|
||||
|
||||
## Test Instances
|
||||
|
||||
| Service | URL | Notes |
|
||||
|---------|-----|-------|
|
||||
| Solar Network | http://solar.local:5000 | Your implementation |
|
||||
| Mastodon | http://mastodon.local:3001 | Test instance |
|
||||
| Mastodon Streaming | http://mastodon.local:4000 | WebSocket |
|
||||
|
||||
## Test Accounts
|
||||
|
||||
### Solar Network
|
||||
- Create via UI or API
|
||||
- Username: `solaruser` (or your choice)
|
||||
|
||||
### Mastodon
|
||||
- Username: `testuser@mastodon.local`
|
||||
- Password: `TestPassword123!`
|
||||
- Role: Admin
|
||||
|
||||
## Quick Test Commands
|
||||
|
||||
### Test WebFinger
|
||||
```bash
|
||||
curl "http://solar.local:5000/.well-known/webfinger?resource=acct:solaruser@solar.local"
|
||||
```
|
||||
|
||||
### Test Actor
|
||||
```bash
|
||||
curl -H "Accept: application/activity+json" \
|
||||
http://solar.local:5000/activitypub/actors/solaruser
|
||||
```
|
||||
|
||||
### Test Outbox
|
||||
```bash
|
||||
curl -H "Accept: application/activity+json" \
|
||||
http://solar.local:5000/activitypub/actors/solaruser/outbox
|
||||
```
|
||||
|
||||
### Test Follow (from Mastodon)
|
||||
1. Open http://mastodon.local:3001
|
||||
2. Log in as `testuser@mastodon.local`
|
||||
3. Search for `@solaruser@solar.local`
|
||||
4. Click Follow
|
||||
|
||||
### Test Follow (from Solar Network to Mastodon)
|
||||
```bash
|
||||
# Send Follow activity to Solar Network
|
||||
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://solar.local:5000/follow-1",
|
||||
"type": "Follow",
|
||||
"actor": "https://solar.local:5000/activitypub/actors/solaruser",
|
||||
"object": "http://mastodon.local:3001/users/testuser"
|
||||
}'
|
||||
```
|
||||
|
||||
## Documentation Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `ACTIVITYPUB_TESTING_GUIDE.md` | Comprehensive testing guide |
|
||||
| `ACTIVITYPUB_TESTING_QUICKREF.md` | Quick command reference |
|
||||
| `ACTIVITYPUB_IMPLEMENTATION.md` | Implementation details |
|
||||
| `ACTIVITYPUB_SUMMARY.md` | Feature summary |
|
||||
| `ACTIVITYPUB_PLAN.md` | Original implementation plan |
|
||||
|
||||
## Database Checks
|
||||
|
||||
### Connect to Database
|
||||
```bash
|
||||
psql -d dyson_network
|
||||
```
|
||||
|
||||
### View Actors
|
||||
```sql
|
||||
SELECT uri, username, display_name, created_at
|
||||
FROM fediverse_actors;
|
||||
```
|
||||
|
||||
### View Contents
|
||||
```sql
|
||||
SELECT uri, type, content, actor_id, created_at
|
||||
FROM fediverse_contents
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
### View Relationships
|
||||
```sql
|
||||
SELECT state, is_following, is_followed_by, created_at
|
||||
FROM fediverse_relationships;
|
||||
```
|
||||
|
||||
### View Activities
|
||||
```sql
|
||||
SELECT type, status, error_message, created_at
|
||||
FROM fediverse_activities
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
## Logs
|
||||
|
||||
### Solar Network Logs
|
||||
```bash
|
||||
# Live logs
|
||||
dotnet run --project DysonNetwork.Sphere
|
||||
|
||||
# Follow ActivityPub activity
|
||||
dotnet run --project DysonNetwork.Sphere 2>&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 <<EOF
|
||||
TRUNCATE fediverse_activities CASCADE;
|
||||
TRUNCATE fediverse_relationships CASCADE;
|
||||
TRUNCATE fediverse_reactions CASCADE;
|
||||
TRUNCATE fediverse_contents CASCADE;
|
||||
TRUNCATE fediverse_actors CASCADE;
|
||||
TRUNCATE fediverse_instances CASCADE;
|
||||
UPDATE publishers SET meta = NULL WHERE meta IS NOT NULL;
|
||||
EOF
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Mastodon won't start
|
||||
|
||||
```bash
|
||||
# Check logs
|
||||
docker compose -f docker-compose.mastodon-test.yml logs -f web
|
||||
|
||||
# Restart
|
||||
docker compose -f docker-compose.mastodon-test.yml restart
|
||||
|
||||
# Recreate
|
||||
docker compose -f docker-compose.mastodon-test.yml down
|
||||
docker compose -f docker-compose.mastodon-test.yml up -d
|
||||
```
|
||||
|
||||
### Can't connect to Solar Network
|
||||
|
||||
```bash
|
||||
# Check if running
|
||||
curl http://solar.local:5000
|
||||
|
||||
# Check logs
|
||||
dotnet run --project DysonNetwork.Sphere 2>&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/)
|
||||
275
docs/ACTIVITYPUB_TESTING_README.md
Normal file
275
docs/ACTIVITYPUB_TESTING_README.md
Normal file
@@ -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!** 🚀
|
||||
282
docs/ACTIVITYPUB_TEST_RESULTS_TEMPLATE.md
Normal file
282
docs/ACTIVITYPUB_TEST_RESULTS_TEMPLATE.md
Normal file
@@ -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**: ___________________________________________________________________________
|
||||
|
||||
__________________________________________________________________________
|
||||
|
||||
__________________________________________________________________________
|
||||
|
||||
__________________________________________________________________________
|
||||
|
||||
303
setup-activitypub-test.sh
Executable file
303
setup-activitypub-test.sh
Executable file
@@ -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 <<EOF
|
||||
# Federation
|
||||
LOCAL_DOMAIN=$MASTODON_DOMAIN
|
||||
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
|
||||
SECRET_KEY_BASE=$SECRET_KEY_BASE
|
||||
OTP_SECRET=$OTP_SECRET
|
||||
|
||||
# Defaults
|
||||
SINGLE_USER_MODE=false
|
||||
DEFAULT_LOCALE=en
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}✓ Generated .env.mastodon${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}4. Creating Docker Compose for Mastodon...${NC}"
|
||||
|
||||
cat > docker-compose.mastodon-test.yml <<EOF
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
restart: always
|
||||
image: postgres:14-alpine
|
||||
shm_size: 256mb
|
||||
networks:
|
||||
- mastodon_network
|
||||
environment:
|
||||
POSTGRES_USER: mastodon
|
||||
POSTGRES_PASSWORD: mastodon_password
|
||||
POSTGRES_DB: mastodon
|
||||
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:
|
||||
- "$MASTODON_PORT:3000"
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
- es
|
||||
networks:
|
||||
- mastodon_network
|
||||
volumes:
|
||||
- ./mastodon-data/public:/mastodon/public/system
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
|
||||
interval: 10s
|
||||
retries: 5
|
||||
|
||||
streaming:
|
||||
restart: always
|
||||
image: tootsuite/mastodon:latest
|
||||
env_file: .env.mastodon
|
||||
command: node ./streaming
|
||||
ports:
|
||||
- "4000:4000"
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
networks:
|
||||
- mastodon_network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "fuser -s 4000/tcp || exit 1"]
|
||||
interval: 10s
|
||||
retries: 5
|
||||
|
||||
sidekiq:
|
||||
restart: always
|
||||
image: tootsuite/mastodon:latest
|
||||
env_file: .env.mastodon
|
||||
command: bundle exec sidekiq
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
networks:
|
||||
- mastodon_network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "ps aux | grep '[s]idekiq' || exit 1"]
|
||||
interval: 10s
|
||||
retries: 5
|
||||
|
||||
networks:
|
||||
mastodon_network:
|
||||
driver: bridge
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}✓ Created docker-compose.mastodon-test.yml${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}5. Starting Mastodon...${NC}"
|
||||
|
||||
docker compose -f docker-compose.mastodon-test.yml up -d
|
||||
|
||||
echo -e "${GREEN}✓ Mastodon containers started${NC}"
|
||||
echo ""
|
||||
echo "Waiting for Mastodon to be ready (this may take 2-5 minutes)..."
|
||||
|
||||
# Wait for web service to be healthy
|
||||
MAX_WAIT=300
|
||||
WAIT_TIME=0
|
||||
while [ $WAIT_TIME -lt $MAX_WAIT ]; do
|
||||
if docker compose -f docker-compose.mastodon-test.yml ps web | grep -q "healthy"; then
|
||||
echo -e "${GREEN}✓ Mastodon is ready!${NC}"
|
||||
break
|
||||
fi
|
||||
echo -n "."
|
||||
sleep 5
|
||||
WAIT_TIME=$((WAIT_TIME + 5))
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
if [ $WAIT_TIME -ge $MAX_WAIT ]; then
|
||||
echo -e "${RED}✗ Mastodon failed to start within expected time${NC}"
|
||||
echo "Check logs with: docker compose -f docker-compose.mastodon-test.yml logs"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${YELLOW}6. Creating Mastodon admin account...${NC}"
|
||||
|
||||
docker compose -f docker-compose.mastodon-test.yml exec web \
|
||||
bin/tootctl accounts create \
|
||||
testuser \
|
||||
testuser@$MASTODON_DOMAIN \
|
||||
--email=test@example.com \
|
||||
--confirmed \
|
||||
--role=admin \
|
||||
--approve || true
|
||||
|
||||
echo -e "${GREEN}✓ Created test user: testuser@$MASTODON_DOMAIN${NC}"
|
||||
echo " Password: TestPassword123!"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}7. Applying Solar Network migrations...${NC}"
|
||||
|
||||
cd DysonNetwork.Sphere
|
||||
|
||||
dotnet ef database update
|
||||
|
||||
echo -e "${GREEN}✓ Migrations applied${NC}"
|
||||
echo ""
|
||||
|
||||
echo ""
|
||||
echo "======================================"
|
||||
echo -e "${GREEN}Setup Complete!${NC}"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
echo "Test Instances:"
|
||||
echo " Solar Network: http://$SOLAR_DOMAIN:$SOLAR_PORT"
|
||||
echo " Mastodon: http://$MASTODON_DOMAIN:$MASTODON_PORT"
|
||||
echo ""
|
||||
echo "Test Accounts:"
|
||||
echo " Solar Network: Create one in the UI or via API"
|
||||
echo " Mastodon: testuser@$MASTODON_DOMAIN (Password: TestPassword123!)"
|
||||
echo ""
|
||||
echo "Next Steps:"
|
||||
echo " 1. Start Solar Network: dotnet run --project DysonNetwork.Sphere"
|
||||
echo " 2. Create a publisher in Solar Network"
|
||||
echo " 3. Open Mastodon and search for @username@solar.local"
|
||||
echo " 4. Test following and posting"
|
||||
echo ""
|
||||
echo "Testing Commands:"
|
||||
echo " # Test WebFinger"
|
||||
echo " curl \"http://$SOLAR_DOMAIN:$SOLAR_PORT/.well-known/webfinger?resource=acct:username@$SOLAR_DOMAIN\""
|
||||
echo ""
|
||||
echo " # Test Actor"
|
||||
echo " curl -H \"Accept: application/activity+json\" \\"
|
||||
echo " http://$SOLAR_DOMAIN:$SOLAR_PORT/activitypub/actors/username"
|
||||
echo ""
|
||||
echo " # Test Outbox"
|
||||
echo " curl -H \"Accept: application/activity+json\" \\"
|
||||
echo " http://$SOLAR_DOMAIN:$SOLAR_PORT/activitypub/actors/username/outbox"
|
||||
echo ""
|
||||
echo " # View logs"
|
||||
echo " docker compose -f docker-compose.mastodon-test.yml logs -f"
|
||||
echo ""
|
||||
echo " # Stop Mastodon"
|
||||
echo " docker compose -f docker-compose.mastodon-test.yml down"
|
||||
echo ""
|
||||
echo "For detailed testing guide, see: ACTIVITYPUB_TESTING_GUIDE.md"
|
||||
echo "For quick reference, see: ACTIVITYPUB_TESTING_QUICKREF.md"
|
||||
echo ""
|
||||
155
test-activitypub.sh
Executable file
155
test-activitypub.sh
Executable file
@@ -0,0 +1,155 @@
|
||||
#!/bin/bash
|
||||
# Quick validation tests for ActivityPub implementation
|
||||
|
||||
set -e
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
SOLAR_URL="http://solar.local:5000"
|
||||
MASTODON_URL="http://mastodon.local:3001"
|
||||
TEST_USER="solaruser"
|
||||
|
||||
echo -e "${BLUE}================================"
|
||||
echo "ActivityPub Validation Tests"
|
||||
echo "================================${NC}"
|
||||
echo ""
|
||||
|
||||
# Test 1: WebFinger
|
||||
echo -e "${YELLOW}[1/7] Testing WebFinger...${NC}"
|
||||
WEBFINGER_RESPONSE=$(curl -s "http://solar.local:5000/.well-known/webfinger?resource=acct:$TEST_USER@solar.local")
|
||||
|
||||
if echo "$WEBFINGER_RESPONSE" | grep -q "subject"; then
|
||||
echo -e "${GREEN}✓ WebFinger is working${NC}"
|
||||
echo "$WEBFINGER_RESPONSE" | jq '.' 2>/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 ""
|
||||
Reference in New Issue
Block a user