A very experimental PLC implementation which uses BFT consensus for decentralization
at main 9.7 kB view raw
1package httpapi 2 3import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "net/http" 9 "net/http/httptest" 10 "testing" 11 "time" 12 13 "github.com/did-method-plc/go-didplc" 14 "github.com/stretchr/testify/require" 15 "tangled.org/gbl08ma.com/didplcbft/plc" 16 "tangled.org/gbl08ma.com/didplcbft/types" 17) 18 19// MockReadPLC is a mock implementation of the ReadPLC interface for testing. 20type MockReadPLC struct { 21 shouldReturnError bool 22 errorType string 23} 24 25func (m *MockReadPLC) ValidateOperation(ctx context.Context, atHeight plc.TreeVersion, at time.Time, did string, opBytes []byte) error { 26 if m.shouldReturnError { 27 switch m.errorType { 28 case "notfound": 29 return plc.ErrDIDNotFound 30 case "gone": 31 return plc.ErrDIDGone 32 } 33 return fmt.Errorf("internal error") 34 } 35 return nil 36} 37 38func (m *MockReadPLC) Resolve(ctx context.Context, atHeight plc.TreeVersion, did string) (didplc.Doc, error) { 39 if m.shouldReturnError { 40 switch m.errorType { 41 case "notfound": 42 return didplc.Doc{}, plc.ErrDIDNotFound 43 case "gone": 44 return didplc.Doc{}, plc.ErrDIDGone 45 } 46 return didplc.Doc{}, fmt.Errorf("internal error") 47 } 48 return didplc.Doc{ 49 ID: "did:plc:test", 50 }, nil 51} 52 53func (m *MockReadPLC) OperationLog(ctx context.Context, atHeight plc.TreeVersion, did string) ([]didplc.OpEnum, error) { 54 if m.shouldReturnError { 55 if m.errorType == "notfound" { 56 return []didplc.OpEnum{}, plc.ErrDIDNotFound 57 } 58 return []didplc.OpEnum{}, fmt.Errorf("internal error") 59 } 60 return []didplc.OpEnum{}, nil 61} 62 63func (m *MockReadPLC) AuditLog(ctx context.Context, atHeight plc.TreeVersion, did string) ([]didplc.LogEntry, error) { 64 if m.shouldReturnError { 65 if m.errorType == "notfound" { 66 return []didplc.LogEntry{}, plc.ErrDIDNotFound 67 } 68 return []didplc.LogEntry{}, fmt.Errorf("internal error") 69 } 70 return []didplc.LogEntry{}, nil 71} 72 73func (m *MockReadPLC) LastOperation(ctx context.Context, atHeight plc.TreeVersion, did string) (didplc.OpEnum, error) { 74 if m.shouldReturnError { 75 if m.errorType == "notfound" { 76 return didplc.OpEnum{}, plc.ErrDIDNotFound 77 } 78 return didplc.OpEnum{}, fmt.Errorf("internal error") 79 } 80 return didplc.OpEnum{}, nil 81} 82 83func (m *MockReadPLC) Data(ctx context.Context, atHeight plc.TreeVersion, did string) (didplc.RegularOp, error) { 84 if m.shouldReturnError { 85 switch m.errorType { 86 case "notfound": 87 return didplc.RegularOp{}, plc.ErrDIDNotFound 88 case "gone": 89 return didplc.RegularOp{}, plc.ErrDIDGone 90 } 91 return didplc.RegularOp{}, fmt.Errorf("internal error") 92 } 93 return didplc.RegularOp{}, nil 94} 95 96func (m *MockReadPLC) Export(ctx context.Context, atHeight plc.TreeVersion, after uint64, count int) ([]types.SequencedLogEntry, error) { 97 if m.shouldReturnError { 98 return []types.SequencedLogEntry{}, fmt.Errorf("internal error") 99 } 100 return []types.SequencedLogEntry{}, nil 101} 102 103func TestServer(t *testing.T) { 104 mockPLC := &MockReadPLC{} 105 106 t.Run("Test Resolve DID", func(t *testing.T) { 107 server, err := NewServer(mockPLC, nil, "tcp://127.0.0.1:8080", 15*time.Second) 108 require.NoError(t, err) 109 110 req, err := http.NewRequest("GET", "/did:plc:test", nil) 111 require.NoError(t, err) 112 113 rr := httptest.NewRecorder() 114 server.router.ServeHTTP(rr, req) 115 116 require.Equal(t, http.StatusOK, rr.Code) 117 require.Contains(t, rr.Body.String(), "did:plc:test") 118 }) 119 120 t.Run("Test Resolve DID Not Found", func(t *testing.T) { 121 mockPLC := &MockReadPLC{shouldReturnError: true, errorType: "notfound"} 122 server, err := NewServer(mockPLC, nil, "tcp://127.0.0.1:8080", 15*time.Second) 123 require.NoError(t, err) 124 125 req, err := http.NewRequest("GET", "/did:plc:test", nil) 126 require.NoError(t, err) 127 128 rr := httptest.NewRecorder() 129 server.router.ServeHTTP(rr, req) 130 131 require.Equal(t, http.StatusNotFound, rr.Code) 132 require.Contains(t, rr.Body.String(), "DID not registered: did:plc:test") 133 }) 134 135 t.Run("Test Resolve DID Gone", func(t *testing.T) { 136 mockPLC := &MockReadPLC{shouldReturnError: true, errorType: "gone"} 137 server, err := NewServer(mockPLC, nil, "tcp://127.0.0.1:8080", 15*time.Second) 138 require.NoError(t, err) 139 140 req, err := http.NewRequest("GET", "/did:plc:test", nil) 141 require.NoError(t, err) 142 143 rr := httptest.NewRecorder() 144 server.router.ServeHTTP(rr, req) 145 146 require.Equal(t, http.StatusGone, rr.Code) 147 require.Contains(t, rr.Body.String(), "DID not available: did:plc:test") 148 }) 149 150 t.Run("Test Resolve DID Internal Error", func(t *testing.T) { 151 mockPLC := &MockReadPLC{shouldReturnError: true, errorType: "internal"} 152 server, err := NewServer(mockPLC, nil, "tcp://127.0.0.1:8080", 15*time.Second) 153 require.NoError(t, err) 154 155 req, err := http.NewRequest("GET", "/did:plc:test", nil) 156 require.NoError(t, err) 157 158 rr := httptest.NewRecorder() 159 server.router.ServeHTTP(rr, req) 160 161 require.Equal(t, http.StatusInternalServerError, rr.Code) 162 require.Contains(t, rr.Body.String(), "Internal server error") 163 }) 164 165 t.Run("Test Create PLC Operation", func(t *testing.T) { 166 server, err := NewServer(mockPLC, nil, "tcp://127.0.0.1:8080", 15*time.Second) 167 require.NoError(t, err) 168 169 op := map[string]interface{}{ 170 "type": "plc_operation", 171 "rotationKeys": []string{"did:key:test"}, 172 "verificationMethods": map[string]string{"atproto": "did:key:test"}, 173 "alsoKnownAs": []string{"at://test"}, 174 "services": map[string]interface{}{"atproto_pds": map[string]string{"type": "AtprotoPersonalDataServer", "endpoint": "https://test.com"}}, 175 "prev": nil, 176 "sig": "test", 177 } 178 opBytes, _ := json.Marshal(op) 179 180 req, err := http.NewRequest("POST", "/did:plc:test", bytes.NewBuffer(opBytes)) 181 require.NoError(t, err) 182 183 rr := httptest.NewRecorder() 184 server.router.ServeHTTP(rr, req) 185 186 require.Equal(t, http.StatusOK, rr.Code) 187 }) 188 189 t.Run("Test Get PLC Log", func(t *testing.T) { 190 server, err := NewServer(mockPLC, nil, "tcp://127.0.0.1:8080", 15*time.Second) 191 require.NoError(t, err) 192 193 req, err := http.NewRequest("GET", "/did:plc:test/log", nil) 194 require.NoError(t, err) 195 196 rr := httptest.NewRecorder() 197 server.router.ServeHTTP(rr, req) 198 199 require.Equal(t, http.StatusOK, rr.Code) 200 }) 201 202 t.Run("Test Get PLC Log Not Found", func(t *testing.T) { 203 mockPLC := &MockReadPLC{shouldReturnError: true, errorType: "notfound"} 204 server, err := NewServer(mockPLC, nil, "tcp://127.0.0.1:8080", 15*time.Second) 205 require.NoError(t, err) 206 207 req, err := http.NewRequest("GET", "/did:plc:test/log", nil) 208 require.NoError(t, err) 209 210 rr := httptest.NewRecorder() 211 server.router.ServeHTTP(rr, req) 212 213 require.Equal(t, http.StatusNotFound, rr.Code) 214 require.Contains(t, rr.Body.String(), "DID not registered: did:plc:test") 215 }) 216 217 t.Run("Test Get PLC Audit Log", func(t *testing.T) { 218 server, err := NewServer(mockPLC, nil, "tcp://127.0.0.1:8080", 15*time.Second) 219 require.NoError(t, err) 220 221 req, err := http.NewRequest("GET", "/did:plc:test/log/audit", nil) 222 require.NoError(t, err) 223 224 rr := httptest.NewRecorder() 225 server.router.ServeHTTP(rr, req) 226 227 require.Equal(t, http.StatusOK, rr.Code) 228 }) 229 230 t.Run("Test Get Last Operation", func(t *testing.T) { 231 server, err := NewServer(mockPLC, nil, "tcp://127.0.0.1:8080", 15*time.Second) 232 require.NoError(t, err) 233 234 req, err := http.NewRequest("GET", "/did:plc:test/log/last", nil) 235 require.NoError(t, err) 236 237 rr := httptest.NewRecorder() 238 server.router.ServeHTTP(rr, req) 239 240 require.Equal(t, http.StatusOK, rr.Code) 241 }) 242 243 t.Run("Test Get Last Operation Internal Error", func(t *testing.T) { 244 mockPLC := &MockReadPLC{shouldReturnError: true, errorType: "internal"} 245 server, err := NewServer(mockPLC, nil, "tcp://127.0.0.1:8080", 15*time.Second) 246 require.NoError(t, err) 247 248 req, err := http.NewRequest("GET", "/did:plc:test/log/last", nil) 249 require.NoError(t, err) 250 251 rr := httptest.NewRecorder() 252 server.router.ServeHTTP(rr, req) 253 254 require.Equal(t, http.StatusInternalServerError, rr.Code) 255 require.Contains(t, rr.Body.String(), "Internal server error") 256 }) 257 258 t.Run("Test Get PLC Data", func(t *testing.T) { 259 server, err := NewServer(mockPLC, nil, "tcp://127.0.0.1:8080", 15*time.Second) 260 require.NoError(t, err) 261 262 req, err := http.NewRequest("GET", "/did:plc:test/data", nil) 263 require.NoError(t, err) 264 265 rr := httptest.NewRecorder() 266 server.router.ServeHTTP(rr, req) 267 268 require.Equal(t, http.StatusOK, rr.Code) 269 }) 270 271 t.Run("Test Get PLC Data Not Found", func(t *testing.T) { 272 mockPLC := &MockReadPLC{shouldReturnError: true, errorType: "notfound"} 273 server, err := NewServer(mockPLC, nil, "tcp://127.0.0.1:8080", 15*time.Second) 274 require.NoError(t, err) 275 276 req, err := http.NewRequest("GET", "/did:plc:test/data", nil) 277 require.NoError(t, err) 278 279 rr := httptest.NewRecorder() 280 server.router.ServeHTTP(rr, req) 281 282 require.Equal(t, http.StatusNotFound, rr.Code) 283 require.Contains(t, rr.Body.String(), "DID not registered: did:plc:test") 284 }) 285 286 t.Run("Test Export", func(t *testing.T) { 287 server, err := NewServer(mockPLC, nil, "tcp://127.0.0.1:8080", 15*time.Second) 288 require.NoError(t, err) 289 290 req, err := http.NewRequest("GET", "/export?count=10", nil) 291 require.NoError(t, err) 292 293 rr := httptest.NewRecorder() 294 server.router.ServeHTTP(rr, req) 295 296 require.Equal(t, http.StatusOK, rr.Code) 297 }) 298 299 t.Run("Test Export Internal Error", func(t *testing.T) { 300 mockPLC := &MockReadPLC{shouldReturnError: true, errorType: "internal"} 301 server, err := NewServer(mockPLC, nil, "tcp://127.0.0.1:8080", 15*time.Second) 302 require.NoError(t, err) 303 304 req, err := http.NewRequest("GET", "/export?count=10", nil) 305 require.NoError(t, err) 306 307 rr := httptest.NewRecorder() 308 server.router.ServeHTTP(rr, req) 309 310 require.Equal(t, http.StatusInternalServerError, rr.Code) 311 require.Contains(t, rr.Body.String(), "Internal server error") 312 }) 313}