atproto libraries implementation in ocaml
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) ]