objective categorical abstract machine language personal data server
65
fork

Configure Feed

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

at main 377 lines 13 kB view raw
1open Alcotest 2open Lwt.Syntax 3open Test_support 4 5let run_lwt f = Lwt_main.run (f ()) 6 7(* helpers *) 8let test_string = testable Fmt.string String.equal 9 10let test_bytes = 11 testable 12 (Fmt.of_to_string (fun b -> String.sub (Bytes.to_string b) 0 10)) 13 Bytes.equal 14 15(** query tests *) 16 17let test_query_success () = 18 run_lwt 19 @@ fun () -> 20 let response = 21 Mock_http.json_response 22 (`Assoc 23 [("did", `String "did:plc:123"); ("handle", `String "test.bsky.social")] 24 ) 25 in 26 let* result, requests = 27 Test_utils.with_mock_responses [response] (fun (module C) client -> 28 C.query client "com.atproto.identity.resolveHandle" 29 (`Assoc [("handle", `String "test.bsky.social")]) 30 (fun json -> 31 let open Yojson.Safe.Util in 32 Ok (json |> member "did" |> to_string) ) ) 33 in 34 check test_string "result" "did:plc:123" result ; 35 check int "request count" 1 (List.length requests) ; 36 let req = List.hd requests in 37 Test_utils.assert_request_path "/xrpc/com.atproto.identity.resolveHandle" req ; 38 Test_utils.assert_request_method `GET req ; 39 Test_utils.assert_request_query_param "handle" "test.bsky.social" req ; 40 Lwt.return_unit 41 42let test_query_with_multiple_params () = 43 run_lwt 44 @@ fun () -> 45 let response = Mock_http.json_response (`Assoc [("followers", `List [])]) in 46 let* _, requests = 47 Test_utils.with_mock_responses [response] (fun (module C) client -> 48 C.query client "app.bsky.graph.getFollowers" 49 (`Assoc 50 [ ("actor", `String "did:plc:123") 51 ; ("limit", `Int 50) 52 ; ("cursor", `String "abc123") ] ) 53 (fun _ -> Ok ()) ) 54 in 55 let req = List.hd requests in 56 Test_utils.assert_request_query_param "actor" "did:plc:123" req ; 57 Test_utils.assert_request_query_param "limit" "50" req ; 58 Test_utils.assert_request_query_param "cursor" "abc123" req ; 59 Lwt.return_unit 60 61let test_query_error_response () = 62 run_lwt 63 @@ fun () -> 64 let response = 65 Mock_http.error_response ~status:`Bad_request ~error:"InvalidHandle" 66 ~message:"Handle not found" () 67 in 68 let* () = 69 Test_utils.with_mock_responses [response] (fun (module C) client -> 70 Lwt.catch 71 (fun () -> 72 let* _ = 73 C.query client "com.atproto.identity.resolveHandle" 74 (`Assoc [("handle", `String "invalid")]) 75 (fun _ -> Ok ()) 76 in 77 fail "should have raised Xrpc_error" ) 78 (function 79 | Hermes.Xrpc_error {status; error; message} -> 80 check int "status" 400 status ; 81 check test_string "error" "InvalidHandle" error ; 82 check (option test_string) "message" (Some "Handle not found") 83 message ; 84 Lwt.return_unit 85 | e -> 86 Lwt.reraise e ) ) 87 |> Lwt.map fst 88 in 89 Lwt.return_unit 90 91let test_query_empty_response () = 92 run_lwt 93 @@ fun () -> 94 let response = Mock_http.empty_response () in 95 let* result, _ = 96 Test_utils.with_mock_responses [response] (fun (module C) client -> 97 C.query client "some.endpoint" (`Assoc []) (fun _ -> Ok "empty") ) 98 in 99 check test_string "result" "empty" result ; 100 Lwt.return_unit 101 102let test_query_bytes () = 103 run_lwt 104 @@ fun () -> 105 let response = 106 Mock_http.bytes_response ~content_type:"image/jpeg" "fake-image-data" 107 in 108 let* (data, content_type), requests = 109 Test_utils.with_mock_responses [response] (fun (module C) client -> 110 C.query_bytes client "com.atproto.sync.getBlob" 111 (`Assoc [("did", `String "did:plc:123"); ("cid", `String "bafyabc")]) ) 112 in 113 check test_bytes "data" (Bytes.of_string "fake-image-data") data ; 114 check test_string "content_type" "image/jpeg" content_type ; 115 let req = List.hd requests in 116 Test_utils.assert_request_has_header "accept" "*/*" req ; 117 Lwt.return_unit 118 119(** procedure tests *) 120 121let test_procedure_success () = 122 run_lwt 123 @@ fun () -> 124 let response = 125 Mock_http.json_response 126 (`Assoc [("uri", `String "at://did:plc:123/app.bsky.feed.post/abc")]) 127 in 128 let* result, requests = 129 Test_utils.with_mock_responses [response] (fun (module C) client -> 130 C.procedure client "com.atproto.repo.createRecord" (`Assoc []) 131 (Some 132 (`Assoc 133 [ ("repo", `String "did:plc:123") 134 ; ("collection", `String "app.bsky.feed.post") 135 ; ( "record" 136 , `Assoc [("text", `String "This post was sent from PDSls")] 137 ) ] ) ) 138 (fun json -> 139 let open Yojson.Safe.Util in 140 Ok (json |> member "uri" |> to_string) ) ) 141 in 142 check test_string "uri" "at://did:plc:123/app.bsky.feed.post/abc" result ; 143 let req = List.hd requests in 144 Test_utils.assert_request_method `POST req ; 145 Test_utils.assert_request_path "/xrpc/com.atproto.repo.createRecord" req ; 146 Test_utils.assert_request_has_header "content-type" "application/json" req ; 147 Test_utils.assert_request_body_contains "This post was sent from PDSls" req ; 148 Lwt.return_unit 149 150let test_procedure_no_input () = 151 run_lwt 152 @@ fun () -> 153 let response = Mock_http.empty_response () in 154 let* _, requests = 155 Test_utils.with_mock_responses [response] (fun (module C) client -> 156 C.procedure client "com.atproto.server.deleteSession" (`Assoc []) None 157 (fun _ -> Ok () ) ) 158 in 159 let req = List.hd requests in 160 Test_utils.assert_request_method `POST req ; 161 check (option test_string) "body" (Some "") req.body ; 162 Lwt.return_unit 163 164let test_procedure_bytes () = 165 run_lwt 166 @@ fun () -> 167 let response = Mock_http.empty_response () in 168 let* result, requests = 169 Test_utils.with_mock_responses [response] (fun (module C) client -> 170 C.procedure_bytes client "com.atproto.repo.importRepo" (`Assoc []) 171 (Some (Bytes.of_string "fake-car-data")) 172 ~content_type:"application/vnd.ipld.car" ) 173 in 174 check (option (pair test_bytes test_string)) "result" None result ; 175 let req = List.hd requests in 176 Test_utils.assert_request_has_header "content-type" "application/vnd.ipld.car" 177 req ; 178 Test_utils.assert_request_has_header "accept" "*/*" req ; 179 check (option test_string) "body" (Some "fake-car-data") req.body ; 180 Lwt.return_unit 181 182let test_procedure_blob () = 183 run_lwt 184 @@ fun () -> 185 let response = 186 Mock_http.json_response 187 (`Assoc 188 [ ( "blob" 189 , `Assoc 190 [ ("$type", `String "blob") 191 ; ("ref", `Assoc [("$link", `String "bafyabc")]) 192 ; ("mimeType", `String "image/jpeg") 193 ; ("size", `Int 1234) ] ) ] ) 194 in 195 let* result, requests = 196 Test_utils.with_mock_responses [response] (fun (module C) client -> 197 C.procedure_blob client "com.atproto.repo.uploadBlob" (`Assoc []) 198 (Bytes.of_string "fake-image-bytes") ~content_type:"image/jpeg" 199 (fun json -> 200 let open Yojson.Safe.Util in 201 Ok (json |> member "blob" |> member "mimeType" |> to_string) ) ) 202 in 203 check test_string "mimeType" "image/jpeg" result ; 204 let req = List.hd requests in 205 Test_utils.assert_request_has_header "content-type" "image/jpeg" req ; 206 check (option test_string) "body" (Some "fake-image-bytes") req.body ; 207 Lwt.return_unit 208 209(** authentication tests *) 210 211let test_auth_header_added () = 212 run_lwt 213 @@ fun () -> 214 let response = Mock_http.json_response (`Assoc []) in 215 let* _, requests = 216 Test_utils.with_mock_responses [response] (fun (module C) client -> 217 let session = Test_utils.make_test_session () in 218 C.set_session client session ; 219 C.query client "some.endpoint" (`Assoc []) (fun _ -> Ok ()) ) 220 in 221 let req = List.hd requests in 222 Test_utils.assert_request_has_auth_header req ; 223 Lwt.return_unit 224 225let test_session_can_be_cleared () = 226 run_lwt 227 @@ fun () -> 228 let response = Mock_http.json_response (`Assoc []) in 229 let* _, requests = 230 Test_utils.with_mock_responses [response] (fun (module C) client -> 231 let session = Test_utils.make_test_session () in 232 C.set_session client session ; 233 C.clear_session client ; 234 C.query client "some.endpoint" (`Assoc []) (fun _ -> Ok ()) ) 235 in 236 let req = List.hd requests in 237 let has_auth = Cohttp.Header.get req.headers "authorization" in 238 check (option test_string) "no auth header" None has_auth ; 239 Lwt.return_unit 240 241(** error handling tests *) 242 243let test_401_unauthorized () = 244 run_lwt 245 @@ fun () -> 246 let response = 247 Mock_http.error_response ~status:`Unauthorized ~error:"AuthRequired" 248 ~message:"Authentication required" () 249 in 250 let* () = 251 Test_utils.with_mock_responses [response] (fun (module C) client -> 252 Lwt.catch 253 (fun () -> 254 let* _ = 255 C.query client "some.protected.endpoint" (`Assoc []) (fun _ -> 256 Ok () ) 257 in 258 fail "should have raised" ) 259 (function 260 | Hermes.Xrpc_error {status= 401; error= "AuthRequired"; _} -> 261 Lwt.return_unit 262 | e -> 263 Lwt.reraise e ) ) 264 |> Lwt.map fst 265 in 266 Lwt.return_unit 267 268let test_500_server_error () = 269 run_lwt 270 @@ fun () -> 271 let response = 272 Mock_http.error_response ~status:`Internal_server_error 273 ~error:"InternalServerError" () 274 in 275 let* () = 276 Test_utils.with_mock_responses [response] (fun (module C) client -> 277 Lwt.catch 278 (fun () -> 279 let* _ = 280 C.query client "some.endpoint" (`Assoc []) (fun _ -> Ok ()) 281 in 282 fail "Should have raised" ) 283 (function 284 | Hermes.Xrpc_error {status= 500; _} -> 285 Lwt.return_unit 286 | e -> 287 Lwt.reraise e ) ) 288 |> Lwt.map fst 289 in 290 Lwt.return_unit 291 292let test_malformed_error_response () = 293 run_lwt 294 @@ fun () -> 295 let response = 296 { Mock_http.status= `Bad_request 297 ; headers= [("content-type", "application/json")] 298 ; body= "not valid json" } 299 in 300 let* () = 301 Test_utils.with_mock_responses [response] (fun (module C) client -> 302 Lwt.catch 303 (fun () -> 304 let* _ = 305 C.query client "some.endpoint" (`Assoc []) (fun _ -> Ok ()) 306 in 307 fail "should have raised" ) 308 (function 309 | Hermes.Xrpc_error {status= 400; error= "UnknownError"; _} -> 310 Lwt.return_unit 311 | e -> 312 Lwt.reraise e ) ) 313 |> Lwt.map fst 314 in 315 Lwt.return_unit 316 317(** client creation tests *) 318 319let test_make_client () = 320 let client = Hermes.make_client ~service:"https://api.bsky.app" () in 321 let service = Hermes.get_service client in 322 check (option test_string) "host" (Some "api.bsky.app") (Uri.host service) 323 324let test_client_service_urls () = 325 let urls = 326 [ "https://bsky.social" 327 ; "https://api.bsky.app" 328 ; "http://localhost:3000" 329 ; "https://pds.example.com:8080" ] 330 in 331 List.iter 332 (fun url -> 333 let client = Hermes.make_client ~service:url () in 334 let service = Hermes.get_service client in 335 check bool "service set" true (String.length (Uri.to_string service) > 0) ) 336 urls 337 338let test_get_session_unauthenticated () = 339 let client = Hermes.make_client ~service:"https://example.com" () in 340 check (option reject) "no session" None (Hermes.get_session client) 341 342(** tests *) 343 344let query_tests = 345 [ ("query success", `Quick, test_query_success) 346 ; ("query with multiple params", `Quick, test_query_with_multiple_params) 347 ; ("query error response", `Quick, test_query_error_response) 348 ; ("query empty response", `Quick, test_query_empty_response) 349 ; ("query bytes", `Quick, test_query_bytes) ] 350 351let procedure_tests = 352 [ ("procedure success", `Quick, test_procedure_success) 353 ; ("procedure no input", `Quick, test_procedure_no_input) 354 ; ("procedure bytes", `Quick, test_procedure_bytes) 355 ; ("procedure blob", `Quick, test_procedure_blob) ] 356 357let auth_tests = 358 [ ("auth header added", `Quick, test_auth_header_added) 359 ; ("session can be cleared", `Quick, test_session_can_be_cleared) ] 360 361let error_tests = 362 [ ("401 unauthorized", `Quick, test_401_unauthorized) 363 ; ("500 server error", `Quick, test_500_server_error) 364 ; ("malformed error response", `Quick, test_malformed_error_response) ] 365 366let creation_tests = 367 [ ("make_client", `Quick, test_make_client) 368 ; ("service URLs", `Quick, test_client_service_urls) 369 ; ("get_session unauthenticated", `Quick, test_get_session_unauthenticated) ] 370 371let () = 372 run "Client" 373 [ ("query", query_tests) 374 ; ("procedure", procedure_tests) 375 ; ("auth", auth_tests) 376 ; ("errors", error_tests) 377 ; ("creation", creation_tests) ]