A very experimental PLC implementation which uses BFT consensus for decentralization
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}