atproto libraries implementation in ocaml
at main 9.9 kB view raw
1(** Tests for the high-level API client *) 2 3open Atproto_api 4 5(** {1 RichText Tests} *) 6 7let test_richtext_of_string () = 8 let rt = Richtext.of_string "Hello world" in 9 Alcotest.(check string) "text" "Hello world" (Richtext.text rt); 10 Alcotest.(check int) "no facets" 0 (List.length (Richtext.facets rt)) 11 12let test_richtext_byte_length () = 13 let rt = Richtext.of_string "Hello" in 14 Alcotest.(check int) "byte length" 5 (Richtext.byte_length rt) 15 16let test_richtext_grapheme_length () = 17 let rt = Richtext.of_string "Hello" in 18 Alcotest.(check int) "grapheme length" 5 (Richtext.grapheme_length rt) 19 20let test_richtext_exceeds_limit () = 21 let short = Richtext.of_string "Hello" in 22 Alcotest.(check bool) 23 "short doesn't exceed" false 24 (Richtext.exceeds_limit short); 25 let long = Richtext.of_string (String.make 400 'a') in 26 Alcotest.(check bool) "long exceeds" true (Richtext.exceeds_limit long) 27 28let test_richtext_truncate () = 29 let long = Richtext.of_string (String.make 400 'a') in 30 let truncated = Richtext.truncate ~limit:100 long in 31 Alcotest.(check bool) 32 "truncated fits" false 33 (Richtext.exceeds_limit ~limit:100 truncated); 34 Alcotest.(check int) "truncated length" 100 (Richtext.byte_length truncated) 35 36let test_find_mentions () = 37 let text = "Hello @alice.bsky.social and @bob.test!" in 38 let mentions = Richtext.find_mentions text in 39 Alcotest.(check int) "two mentions" 2 (List.length mentions); 40 let start1, end1, handle1 = List.nth mentions 0 in 41 Alcotest.(check int) "first start" 6 start1; 42 Alcotest.(check int) "first end" 24 end1; 43 Alcotest.(check string) "first handle" "alice.bsky.social" handle1; 44 let _, _, handle2 = List.nth mentions 1 in 45 Alcotest.(check string) "second handle" "bob.test" handle2 46 47let test_find_mentions_no_domain () = 48 let text = "Hello @alice!" in 49 let mentions = Richtext.find_mentions text in 50 Alcotest.(check int) "no mentions without domain" 0 (List.length mentions) 51 52let test_find_urls () = 53 let text = "Check https://example.com and http://test.org" in 54 let urls = Richtext.find_urls text in 55 Alcotest.(check int) "two urls" 2 (List.length urls); 56 let _, _, url1 = List.nth urls 0 in 57 Alcotest.(check string) "first url" "https://example.com" url1; 58 let _, _, url2 = List.nth urls 1 in 59 Alcotest.(check string) "second url" "http://test.org" url2 60 61let test_find_tags () = 62 let text = "Hello #ocaml and #atproto!" in 63 let tags = Richtext.find_tags text in 64 Alcotest.(check int) "two tags" 2 (List.length tags); 65 let _, _, tag1 = List.nth tags 0 in 66 Alcotest.(check string) "first tag" "ocaml" tag1; 67 let _, _, tag2 = List.nth tags 1 in 68 Alcotest.(check string) "second tag" "atproto" tag2 69 70let test_detect_facets () = 71 let text = "Hello @alice.bsky.social! Check https://example.com #test" in 72 let rt = Richtext.detect_facets text in 73 Alcotest.(check string) "text preserved" text (Richtext.text rt); 74 Alcotest.(check int) "three facets" 3 (List.length (Richtext.facets rt)) 75 76let test_add_facet () = 77 let rt = Richtext.of_string "Hello @alice!" in 78 let facet = Richtext.mention_facet ~start:6 ~end_:12 ~did:"did:plc:test" in 79 let rt = Richtext.add_facet rt facet in 80 Alcotest.(check int) "one facet" 1 (List.length (Richtext.facets rt)) 81 82let test_richtext_to_json () = 83 let rt = Richtext.of_string "Hello" in 84 let json = Richtext.to_json rt in 85 match json with 86 | Simdjsont.Json.Object pairs -> 87 Alcotest.(check bool) "has text" true (List.mem_assoc "text" pairs); 88 Alcotest.(check bool) 89 "no facets key" false 90 (List.mem_assoc "facets" pairs) 91 | _ -> Alcotest.fail "expected object" 92 93let test_richtext_to_json_with_facets () = 94 let rt = Richtext.of_string "Hello @alice.bsky.social" in 95 let facet = Richtext.mention_facet ~start:6 ~end_:24 ~did:"did:plc:test" in 96 let rt = Richtext.add_facet rt facet in 97 let json = Richtext.to_json rt in 98 match json with 99 | Simdjsont.Json.Object pairs -> 100 Alcotest.(check bool) "has text" true (List.mem_assoc "text" pairs); 101 Alcotest.(check bool) "has facets" true (List.mem_assoc "facets" pairs) 102 | _ -> Alcotest.fail "expected object" 103 104let test_richtext_of_json () = 105 let json : Richtext.json = 106 Simdjsont.Json.Object 107 [ 108 ("text", Simdjsont.Json.String "Hello"); 109 ( "facets", 110 Simdjsont.Json.Array 111 [ 112 Simdjsont.Json.Object 113 [ 114 ( "index", 115 Simdjsont.Json.Object 116 [ 117 ("byteStart", Simdjsont.Json.Int 0L); 118 ("byteEnd", Simdjsont.Json.Int 5L); 119 ] ); 120 ( "features", 121 Simdjsont.Json.Array 122 [ 123 Simdjsont.Json.Object 124 [ 125 ( "$type", 126 Simdjsont.Json.String 127 "app.bsky.richtext.facet#mention" ); 128 ("did", Simdjsont.Json.String "did:plc:test"); 129 ]; 130 ] ); 131 ]; 132 ] ); 133 ] 134 in 135 match Richtext.of_json json with 136 | Some rt -> 137 Alcotest.(check string) "text" "Hello" (Richtext.text rt); 138 Alcotest.(check int) "one facet" 1 (List.length (Richtext.facets rt)) 139 | None -> Alcotest.fail "expected Some" 140 141let test_byte_slice_to_json () = 142 let slice = Richtext.byte_slice ~start:10 ~end_:20 in 143 let json = Richtext.byte_slice_to_json slice in 144 match json with 145 | Simdjsont.Json.Object pairs -> 146 Alcotest.(check (option int)) 147 "byteStart" (Some 10) 148 (match List.assoc_opt "byteStart" pairs with 149 | Some (Simdjsont.Json.Int i) -> Atproto_json.int_of_int64_opt i 150 | _ -> None); 151 Alcotest.(check (option int)) 152 "byteEnd" (Some 20) 153 (match List.assoc_opt "byteEnd" pairs with 154 | Some (Simdjsont.Json.Int i) -> Atproto_json.int_of_int64_opt i 155 | _ -> None) 156 | _ -> Alcotest.fail "expected object" 157 158let test_feature_to_json_mention () = 159 let feature = Richtext.Mention { did = "did:plc:test" } in 160 let json = Richtext.feature_to_json feature in 161 match json with 162 | Simdjsont.Json.Object pairs -> 163 Alcotest.(check (option string)) 164 "$type" (Some "app.bsky.richtext.facet#mention") 165 (match List.assoc_opt "$type" pairs with 166 | Some (Simdjsont.Json.String s) -> Some s 167 | _ -> None) 168 | _ -> Alcotest.fail "expected object" 169 170let test_feature_to_json_link () = 171 let feature = Richtext.Link { uri = "https://example.com" } in 172 let json = Richtext.feature_to_json feature in 173 match json with 174 | Simdjsont.Json.Object pairs -> 175 Alcotest.(check (option string)) 176 "$type" (Some "app.bsky.richtext.facet#link") 177 (match List.assoc_opt "$type" pairs with 178 | Some (Simdjsont.Json.String s) -> Some s 179 | _ -> None) 180 | _ -> Alcotest.fail "expected object" 181 182let test_feature_to_json_tag () = 183 let feature = Richtext.Tag { tag = "ocaml" } in 184 let json = Richtext.feature_to_json feature in 185 match json with 186 | Simdjsont.Json.Object pairs -> 187 Alcotest.(check (option string)) 188 "$type" (Some "app.bsky.richtext.facet#tag") 189 (match List.assoc_opt "$type" pairs with 190 | Some (Simdjsont.Json.String s) -> Some s 191 | _ -> None) 192 | _ -> Alcotest.fail "expected object" 193 194(** {1 Agent Tests} *) 195 196let test_agent_create () = 197 let agent = Agent.create ~pds:(Uri.of_string "https://bsky.social") in 198 Alcotest.(check bool) "not authenticated" false (Agent.is_authenticated agent); 199 Alcotest.(check (option string)) "no did" None (Agent.did agent); 200 Alcotest.(check (option string)) "no handle" None (Agent.handle agent) 201 202let test_agent_create_from_url () = 203 let agent = Agent.create_from_url ~url:"https://bsky.social" in 204 Alcotest.(check bool) "not authenticated" false (Agent.is_authenticated agent) 205 206let test_error_to_string () = 207 let errors = 208 [ 209 Agent.Not_authenticated; 210 Agent.Parse_error "test"; 211 Agent.Invalid_response "test"; 212 ] 213 in 214 List.iter 215 (fun e -> 216 let s = Agent.error_to_string e in 217 Alcotest.(check bool) "error string not empty" true (String.length s > 0)) 218 errors 219 220(** {1 Test Suites} *) 221 222let richtext_tests = 223 [ 224 Alcotest.test_case "of_string" `Quick test_richtext_of_string; 225 Alcotest.test_case "byte_length" `Quick test_richtext_byte_length; 226 Alcotest.test_case "grapheme_length" `Quick test_richtext_grapheme_length; 227 Alcotest.test_case "exceeds_limit" `Quick test_richtext_exceeds_limit; 228 Alcotest.test_case "truncate" `Quick test_richtext_truncate; 229 Alcotest.test_case "find_mentions" `Quick test_find_mentions; 230 Alcotest.test_case "find_mentions_no_domain" `Quick 231 test_find_mentions_no_domain; 232 Alcotest.test_case "find_urls" `Quick test_find_urls; 233 Alcotest.test_case "find_tags" `Quick test_find_tags; 234 Alcotest.test_case "detect_facets" `Quick test_detect_facets; 235 Alcotest.test_case "add_facet" `Quick test_add_facet; 236 Alcotest.test_case "to_json" `Quick test_richtext_to_json; 237 Alcotest.test_case "to_json_with_facets" `Quick 238 test_richtext_to_json_with_facets; 239 Alcotest.test_case "of_json" `Quick test_richtext_of_json; 240 Alcotest.test_case "byte_slice_to_json" `Quick test_byte_slice_to_json; 241 Alcotest.test_case "feature_to_json_mention" `Quick 242 test_feature_to_json_mention; 243 Alcotest.test_case "feature_to_json_link" `Quick test_feature_to_json_link; 244 Alcotest.test_case "feature_to_json_tag" `Quick test_feature_to_json_tag; 245 ] 246 247let agent_tests = 248 [ 249 Alcotest.test_case "create" `Quick test_agent_create; 250 Alcotest.test_case "create_from_url" `Quick test_agent_create_from_url; 251 Alcotest.test_case "error_to_string" `Quick test_error_to_string; 252 ] 253 254let () = 255 Alcotest.run "atproto-api" 256 [ ("richtext", richtext_tests); ("agent", agent_tests) ]