A very experimental PLC implementation which uses BFT consensus for decentralization
19
fork

Configure Feed

Select the types of activity you want to include in your feed.

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