Seppo.Social -> Seppo.mro.name
[Seppo.git] / test / t_ap.ml
blobb0619cc58e631c50a28f0901600aac83f9c096ee
1 (*
2 * _ _ ____ _
3 * _| || |_/ ___| ___ _ __ _ __ ___ | |
4 * |_ .. _\___ \ / _ \ '_ \| '_ \ / _ \| |
5 * |_ _|___) | __/ |_) | |_) | (_) |_|
6 * |_||_| |____/ \___| .__/| .__/ \___/(_)
7 * |_| |_|
9 * Personal Social Web.
11 * t_as2.ml
13 * Copyright (C) The #Seppo contributors. All rights reserved.
15 * This program is free software: you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation, either version 3 of the License, or
18 * (at your option) any later version.
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
25 * You should have received a copy of the GNU General Public License
26 * along with this program. If not, see <http://www.gnu.org/licenses/>.
30 open Seppo_lib
31 open Alcotest
32 module A = Assrt
34 let set_up () =
35 Mirage_crypto_rng_lwt.initialize (module Mirage_crypto_rng.Fortuna);
36 Unix.chdir "../../../test/"
38 let tc_follower_state () =
39 Logr.info (fun m -> m "%s.%s" "Ap_test" "test_follower_state");
40 Ap.Followers.State.(Pending |> to_string) |>
41 check string __LOC__ "pending"
43 let tc_inbox_follow () =
44 Logr.info (fun m -> m "%s.%s" "Ap_test" "test_inbox_follow");
45 let a = "data/follow.mastodon.json"
46 |> File.in_channel Ezjsonm.from_channel
47 |> As2_vocab.Activitypub.Decode.follow
48 |> Result.get_ok in
49 a.id |> A.equals_uri __LOC__ (Uri.of_string "https://alpaka.social/afe4ac8a-aaeb-41f5-a348-e5d133bdb931");
50 a.actor |> A.equals_uri __LOC__ (Uri.of_string "https://alpaka.social/users/traunstein");
51 a.object_ |> A.equals_uri __LOC__ (Uri.of_string "https://dev.seppo.social/2023-06-12/activitypub/profile.json");
52 match a.state with
53 | None -> ()
54 | _ -> "-" |> A.equals_string __LOC__ ""
56 let tc_inbox_unfollow () =
57 Logr.info (fun m -> m "%s.%s" "Ap_test" "test_inbox_unfollow");
58 let o = "data/undo-follow.mastodon.json"
59 |> File.in_channel Ezjsonm.from_channel
60 |> As2_vocab.Activitypub.Decode.(undo follow)
61 |> Result.get_ok in
62 o.id |> Uri.to_string |> A.equals_string __LOC__ "https://alpaka.social/users/traunstein#follows/5219/undo";
63 o.actor |> Uri.to_string |> A.equals_string __LOC__ "https://alpaka.social/users/traunstein";
64 o.obj.id |> Uri.to_string |> A.equals_string __LOC__ "https://alpaka.social/a5fc3ab5-92f3-4641-a33e-96418e9bec78";
65 o.obj.object_ |> Uri.to_string |> A.equals_string __LOC__ "https://dev.seppo.social/2023-06-12/activitypub/profile.json";
66 assert (o.actor |> Uri.equal o.obj.actor);
68 a.object_ |> A.equals_uri __LOC__ (Uri.of_string "https://dev.seppo.mro.name/2023-06-12/activitypub/profile.json");
69 match a.state with
70 | None -> ()
71 | _ -> "-" |> A.equals_string __LOC__ ""
73 assert true
75 let tc_followers_page_json_raw () =
76 Logr.info (fun m -> m "%s.%s" "Ap_test" "test_followers_page_json_raw");
77 let fs = [
78 "1/" |> Uri.of_string;
79 "2/" |> Uri.of_string;
80 "3/" |> Uri.of_string;
81 "4/" |> Uri.of_string;
82 "5/" |> Uri.of_string;
83 ] in
84 let base' = "https://example.com/u/" |> Uri.of_string in
85 let base = "https://example.com/ap/followers/2.jsa" |> Uri.of_string in
86 let p : Uri.t As2_vocab.Types.collection_page = {
87 id = base;
88 current = Some base;
89 first = None;
90 is_ordered = true;
91 items = fs;
92 last = Some (Http.reso ~base (Uri.of_string "0.jsa"));
93 next = Some (Http.reso ~base (Uri.of_string "1.jsa"));
94 part_of = Some (Http.reso ~base (Uri.of_string "index.jsa"));
95 prev = Some (Http.reso ~base (Uri.of_string "3.jsa"));
96 total_items= None;
97 } in
98 p |> As2_vocab.Encode.(collection_page ~base (uri ~base:base'))
99 |> Ezjsonm.value_to_string ~minify:false
100 |> A.equals_string __LOC__ {|{
101 "@context": [
102 "https://www.w3.org/ns/activitystreams",
103 "https://w3id.org/security/v1",
105 "schema": "http://schema.org#",
106 "PropertyValue": "schema:PropertyValue",
107 "value": "schema:value",
108 "@language": "und"
111 "type": "OrderedCollectionPage",
112 "id": "https://example.com/ap/followers/2.jsa",
113 "current": "https://example.com/ap/followers/2.jsa",
114 "last": "https://example.com/ap/followers/0.jsa",
115 "next": "https://example.com/ap/followers/1.jsa",
116 "partOf": "https://example.com/ap/followers/index.jsa",
117 "prev": "https://example.com/ap/followers/3.jsa",
118 "orderedItems": [
119 "https://example.com/u/1/",
120 "https://example.com/u/2/",
121 "https://example.com/u/3/",
122 "https://example.com/u/4/",
123 "https://example.com/u/5/"
127 let tc_followers_page_json () =
128 Logr.info (fun m -> m "%s.%s" "Ap_test" "test_followers_page_json");
129 let foo = "https://example.com/foo/usr/" |> Uri.of_string in
130 let base = "https://example.com/" |> Uri.of_string in
131 let path = Ap.apub ^ "followers/" in
132 let base = Http.reso ~base (Uri.make ~path ()) in
133 [ "1/"; "2/"; "3/"; "4/"; "5/" ]
134 |> List.rev
135 |> List.fold_left (fun init path -> (Uri.make ~path () |> Http.reso ~base:foo) :: init) []
136 |> Ap.Followers.Json.to_page_json ~base path ~is_last:false 2
137 |> Ezjsonm.value_to_string ~minify:false
138 |> A.equals_string __LOC__ {|{
139 "@context": [
140 "https://www.w3.org/ns/activitystreams",
141 "https://w3id.org/security/v1",
143 "schema": "http://schema.org#",
144 "PropertyValue": "schema:PropertyValue",
145 "value": "schema:value",
146 "@language": "und"
149 "type": "OrderedCollectionPage",
150 "id": "https://example.com/activitypub/followers/2.jsa",
151 "current": "https://example.com/activitypub/followers/2.jsa",
152 "last": "https://example.com/activitypub/followers/0.jsa",
153 "next": "https://example.com/activitypub/followers/1.jsa",
154 "partOf": "https://example.com/activitypub/followers/index.jsa",
155 "prev": "https://example.com/activitypub/followers/3.jsa",
156 "orderedItems": [
157 "https://example.com/foo/usr/1/",
158 "https://example.com/foo/usr/2/",
159 "https://example.com/foo/usr/3/",
160 "https://example.com/foo/usr/4/",
161 "https://example.com/foo/usr/5/"
165 let tc_followers_json () =
166 Logr.info (fun m -> m "%s.%s" "Ap_test" "test_followers_json");
167 let foo = "https://example.com/foo/usr/" |> Uri.of_string in
168 let lst = [ "1/"; "2/"; "3/"; "4/"; "5/" ]
169 |> List.rev
170 |> List.fold_left (fun init path -> (Uri.make ~path () |> Http.reso ~base:foo) :: init) []
172 let base = "https://example.com/" |> Uri.of_string in
173 let path = Ap.apub ^ "followers/" in
174 let base = Http.reso ~base (Uri.make ~path ()) in
175 let _ = File.mkdir_p File.pDir path in
176 (* let oc = stdout in *)
177 "/dev/null" |> File.out_channel_append (fun oc ->
178 Ap.Followers.Json.(List.fold_left
179 (fold2pages 3 (flush_page_json ~base ~oc path))
180 (0,0,[],0)
182 |> flush_page_json ~base ~is_last:true ~oc path);
183 (* check existence of index.jsa *)
184 (* check existence of 0.jsa *)
185 (* check existence of 1.jsa *)
186 "" |> A.equals_string __LOC__ ""
189 let tc_followers_cdb_json () =
190 Logr.info (fun m -> m "%s.%s" "Ap_test" "test_followers_cdb_json");
191 "/dev/null" |> File.out_channel_append (fun oc ->
192 let base = "https://example.com/" |> Uri.of_string in
193 let path = Ap.apub ^ "follower2/" in
194 let base = Http.reso ~base (Uri.make ~path ()) in
195 let path = "tmp/" ^ path in
196 let _ = File.mkdir_p File.pDir path in
197 let _ = Ap.Followers.Json.coll_of_cdb ~base ~oc ~pagesize:3 path (Mapcdb.Cdb "data/followers.cdb") in
198 "" |> A.equals_string __LOC__ ""
201 module Note = struct
202 let tc_of_rfc4287 () =
203 let open Rfc4287 in
204 let e = {Entry.empty with
205 id = "" |> Uri.of_string;
206 lang = Rfc4646 "nl";
207 title = "Title";
208 published = Rfc3339.T "2023-10-24T11:12:13+02:00";
209 updated = Rfc3339.T "2023-10-24T11:12:13+02:00";
210 links = [Link.make ("https://Seppo.mro.name/demo" |> Uri.of_string) ];
211 content = "string";
212 } in
213 e.title |> check string __LOC__ "Title";
214 let n = e |> Ap.Note.of_rfc4287 in
215 let _,su = n.summary_map |> List.hd in
216 su |> check string __LOC__ "Title";
217 n.url |> List.hd |> Uri.to_string |> check string __LOC__ "https://seppo.mro.name/demo"
219 let tc_create_note_json () =
220 Logr.info (fun m -> m "%s.%s" "Ap_test" "test_create_note_json");
221 "data/create.note.mastodon2.json" |> File.in_channel (fun ic ->
222 let c : As2_vocab.Types.note As2_vocab.Types.create = Ezjsonm.from_channel ic
223 |> As2_vocab.Decode.(create note)
224 |> Result.get_ok in
225 assert (not c.direct_message);
226 let n = c.obj in
227 n.id |> Uri.to_string |> A.equals_string __LOC__ "https://bewegung.social/users/mro/statuses/111561416759041219";
228 n.attributed_to |> Uri.to_string |> A.equals_string __LOC__ "https://bewegung.social/users/mro";
229 n.published |> Option.get |> Ptime.to_rfc3339 |> A.equals_string __LOC__ "2023-12-11T10:55:25-00:00";
230 n.in_reply_to |> List.length |> A.equals_int __LOC__ 0;
231 assert (n.summary_map |> List.length = 0);
232 let _,co = n.content_map |> List.hd in
233 co |> A.equals_string __LOC__ {|<p>Good morning 😀 <a href="https://bewegung.social/tags/Social" class="mention hashtag" rel="tag">#<span>Social</span></a> <a href="https://bewegung.social/tags/Web" class="mention hashtag" rel="tag">#<span>Web</span></a>! <span class="h-card" translate="no"><a href="https://seppo.social/demo/" class="u-url mention">@<span>demo</span></a></span></p>|};
234 n.tags |> List.length |> A.equals_int __LOC__ 3;
235 (match n.tags with
237 {ty=`Mention;name=n0;href=h0};
238 {ty=`Hashtag;name=n1;href=h1};
239 {ty=`Hashtag;name=n2;href=h2};
240 ] ->
241 n0 |> A.equals_string __LOC__ "@demo@seppo.social";
242 h0 |> Uri.to_string |> A.equals_string __LOC__ "https://seppo.social/demo/activitypub/profile.jlda";
243 n1 |> A.equals_string __LOC__ "#social";
244 h1 |> Uri.to_string |> A.equals_string __LOC__ "https://bewegung.social/tags/social";
245 n2 |> A.equals_string __LOC__ "#web";
246 h2 |> Uri.to_string |> A.equals_string __LOC__ "https://bewegung.social/tags/web";
248 | _ -> failwith "aua"
249 (**) );
250 assert true
253 let tc_diluviate () =
254 let n =
255 {Ap.Note.empty with
256 id = "https://example.com/1" |> Uri.of_string;
257 sensitive = true;
258 content_map= ["de","Inhalt"];
259 summary_map= ["de","⚠️ Summary"];
260 url = ["https://Seppo.mro.name/demo" |> Uri.of_string];
261 } in
262 let base = Uri.empty in
263 let minify = false in
265 |> Ap.Note.diluviate
266 |> As2_vocab.Encode.note ~base |> Ezjsonm.value_to_string ~minify
267 |> Assrt.equals_string
268 __LOC__ {|{
269 "type": "Note",
270 "id": "https://example.com/1",
271 "attributedTo": "",
272 "mediaType": "text/html; charset=utf8",
273 "contentMap": {
274 "de": "<a href='https://seppo.mro.name/demo'>https://seppo.mro.name/demo</a><br/>\n<br/>\nInhalt"
276 "sensitive": true,
277 "summaryMap": {
278 "de": "⚠️ Summary"
280 "url": "https://example.com/1"
281 }|};
282 assert true
284 let tc_note_to_plain () =
286 test/data/ap/inbox/create/note/note-OZcAekXDY1A.json
291 let tc_create_follow_json () =
292 let to_actor = "http://example.com/to/actor" |> Uri.of_string
293 and from_actor = "http://example.com/from/actor" |> Uri.of_string
294 and to_inbox = "http://example.com/to/inbox" |> Uri.of_string
295 and base = "http://example.com/from/" |> Uri.of_string
296 and tnow = Ptime.of_date_time ((2024,1,2), ((3,4,5),60*60)) |> Option.value ~default:Ptime.max
297 and minify = false in
298 let _fo = to_actor |> Ap.Following.make ~tnow ~me:from_actor ~inbox:to_inbox in
300 |> As2_vocab.Encode.follow ~base
301 |> Ezjsonm.value_to_string ~minify |>
302 Assrt.equals_string
303 __LOC__ {|{
304 "@context": [
305 "https://www.w3.org/ns/activitystreams",
306 "https://w3id.org/security/v1",
308 "schema": "http://schema.org#",
309 "PropertyValue": "schema:PropertyValue",
310 "value": "schema:value",
311 "@language": "und"
314 "type": "Follow",
315 "id": "http://example.com/from/actor#subscribe",
316 "actor": "http://example.com/from/actor",
317 "endTime": "2024-04-03T02:04:05Z",
318 "object": "http://example.com/to/actor"
321 let tc_reject_json () =
322 Logr.info (fun m -> m "%s.%s" "Ap_test" "test_reject_json");
323 let reject me id =
324 `O [("@context", `String "https://www.w3.org/ns/activitystreams");
325 ("type", `String "Reject");
326 ("actor", `String (me |> Uri.to_string));
327 ("object", `String (id |> Uri.to_string))]
329 let me = "https://example.com/alice" |> Uri.of_string in
330 let id = match "data/create.note.mastodon2.json" |> File.in_channel Ezjsonm.from_channel with
331 | `O (_ :: ("id", `String id) :: _) -> id |> Uri.of_string
332 | _ -> Uri.empty in
333 id |> Uri.to_string |> A.equals_string __LOC__ {|https://bewegung.social/users/mro/statuses/111561416759041219/activity|};
335 |> reject me
336 |> Ezjsonm.value_to_string
337 |> A.equals_string __LOC__ {|{"@context":"https://www.w3.org/ns/activitystreams","type":"Reject","actor":"https://example.com/alice","object":"https://bewegung.social/users/mro/statuses/111561416759041219/activity"}|};
338 assert true
340 let tc_subscribed () =
341 Logr.info (fun m -> m "%s" __LOC__);
342 for i = 0 to 10 do
343 try i |> Printf.sprintf "/tmp/%d.xml" |> Unix.unlink
344 with | Unix.Unix_error(Unix.ENOENT, "unlink", _) -> ()
345 done ;
346 let cdb = Mapcdb.Cdb "data/2024-04-30-131146-subscribed.cdb" in
348 |> Mapcdb.fold_left (fun c _ -> 1 + c) 0
349 |> A.equals_int __LOC__ 52;
350 let r = Ap.Followers.Atom.rule in
351 r.target |> A.equals_string __LOC__ "activitypub/subscribers/index.xml";
352 (match Ap.Followers.Atom.of_cdb
353 ~cdb
354 ~base:("https://example.com/" |> Uri.of_string)
355 ~title:"My Title"
356 ~xsl:(Rfc4287.xsl "my.xsl" "./tmp/noop")
357 ~rel:(Some Rfc4287.Link.subscribers)
358 ~page_size:5
359 "./tmp/" with
360 | Error _ -> failwith __LOC__
361 | Ok s -> s)
362 |> A.equals_string __LOC__ "./tmp/10.xml";
363 "./tmp/0.xml" |> File.to_string |> A.equals_string __LOC__ {|<?xml version="1.0"?>
364 <?xml-stylesheet type="text/xsl" href="../../themes/current/my.xsl"?>
365 <feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://example.com/">
366 <title>My Title</title>
367 <id>0.xml</id>
368 <link rel="self" href="0.xml" title="1"/>
369 <link rel="first" href="." title="last"/>
370 <link rel="last" href="0.xml" title="1"/>
371 <link rel="previous" href="1.xml" title="2"/>
372 <link rel="subscribers" href="https://social.wohlfarth.name/users/alain" title=""/>
373 <link rel="subscribers" href="https://hci.social/users/sigchi" title=""/>
374 <link rel="subscribers" href="https://fosstodon.org/users/nlnetlabs" title=""/>
375 <link rel="subscribers" href="https://mastodon.xyz/users/NGIZero" title=""/>
376 <link rel="subscribers" href="https://w3c.social/users/w3c" title=""/>
377 </feed>|};
378 "./tmp/10.xml" |> File.to_string |> A.equals_string __LOC__ {|<?xml version="1.0"?>
379 <?xml-stylesheet type="text/xsl" href="../../themes/current/my.xsl"?>
380 <feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://example.com/">
381 <title>My Title</title>
382 <id>10.xml</id>
383 <link rel="self" href="10.xml" title="11"/>
384 <link rel="first" href="." title="last"/>
385 <link rel="last" href="0.xml" title="1"/>
386 <link rel="previous" href="11.xml" title="12"/>
387 <link rel="next" href="9.xml" title="10"/>
388 <link rel="subscribers" href="https://mas.to/users/leostera" title=""/>
389 <link rel="subscribers" href="https://social.network.europa.eu/users/EU_Commission" title="European Commission" rfc7565="acct:EU_Commission@social.network.europa.eu"/>
390 </feed>|};
391 "?" |> A.equals_string __LOC__ "?"
393 let tc_to_rfc4287_0 () =
394 Logr.info (fun m -> m "%s.%s" "Ap_test" "to_rfc4287_0");
395 let now = ((2024,3,19), ((1,2,3),60*60)) |> Ptime.of_date_time
396 and tz = Timedesc.Time_zone.utc in
397 let n = {Ap.Note.empty with
398 id = "https://example.com/o/42" |> Uri.of_string;
399 attributed_to = "https://example.com/a/23" |> Uri.of_string;
400 content_map = ["en","Some Content"];
401 published = now;
402 summary_map = ["en","No content warning"];
403 } in
404 let now = now |> Option.value ~default:Ptime.epoch in
405 let a = n |> Ap.Note.to_rfc4287 ~tz ~now in
406 a.id |> Uri.to_string |> Assrt.equals_string __LOC__ "https://example.com/o/42";
407 a.author.uri |> Option.value ~default:Uri.empty |> Uri.to_string |> Assrt.equals_string __LOC__ "https://example.com/a/23";
408 let Rfc3339.T t = a.published in
409 t |> Assrt.equals_string __LOC__ "2024-03-19T00:02:03Z";
410 a.title |> Assrt.equals_string __LOC__ "No content warning";
411 a.content |> Assrt.equals_string __LOC__ "Some Content";
414 let tc_to_rfc4287_loaded () =
415 Logr.info (fun m -> m "%s.%s" "Ap_test" "to_rfc4287_loaded");
416 let load_note fn =
417 let fn = "data/ap/inbox/create/note/" ^ fn in
419 |> File.in_channel
420 (fun ic ->
421 match ic |> Ezjsonm.from_channel |> As2_vocab.Activitypub.Decode.obj with
422 | Error _ -> failwith "failed to load note"
423 | Ok o -> match o with
424 | `Create { obj = `Note obj; _ } -> obj
425 | _ -> failwith "strange type")
427 let now = ((2024,3,19), ((1,2,3),60*60)) |> Ptime.of_date_time |> Option.get
428 and tz = Timedesc.Time_zone.utc
429 and base = "https://example.com/fe/" |> Uri.of_string
430 and attr = [ ((Xmlm.ns_xmlns,"xmlns"),Xml.ns_a);
431 ((Xmlm.ns_xmlns,"thr") ,Xml.ns_thr);
432 ((Xmlm.ns_xmlns,"as") ,Xml.ns_as);
433 ((Xmlm.ns_xmlns,"wf") ,Xml.ns_rfc7033); ] in
434 let apu = load_note "note-Hjcb9bqwCgk.json" in
435 let ato = apu |> Ap.Note.to_rfc4287 ~tz ~now in
436 ato.author.name |> Assrt.equals_string __LOC__ "https://mastodon.social/users/nicholas_saunders";
437 ato.author.uri |> Option.value ~default:Uri.empty |> Uri.to_string |> Assrt.equals_string __LOC__ "https://mastodon.social/users/nicholas_saunders";
438 ato.content |> Assrt.equals_string __LOC__ {|@wschenk@floss.social @geoglyphentropy@mstdn.social @nus@mstdn.social @DavidKafri@tooot.im @thetechtutor@me.dm After the Goat refused to explain what military action in response to the #alaqsaflood[1] would've been moral he lost all priveleges with me, although he eventually admitted that nothing would meet his standards. Eventually he blocked me, although he seems to have somehow replied to me.
440 I'm inferring that this has something to with the Goat, but have no idea. Guess I'll never know what it was about.
442 [1]: https://mastodon.social/tags/alaqsaflood|};
443 let buf = 1024 |> Buffer.create in
444 buf |> Xml.to_buf (ato |> Rfc4287.Entry.to_atom ~attr ~base);
445 buf |> Buffer.to_bytes |> Bytes.to_string |> Assrt.equals_string __LOC__ {|<?xml version="1.0"?>
446 <entry xml:lang="en" xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:as="https://www.w3.org/ns/activitystreams" xmlns:wf="urn:ietf:rfc:7033">
447 <id>https://mastodon.social/users/nicholas_saunders/statuses/112615088973179734</id>
448 <title type="text"></title>
449 <updated>2024-06-14T12:58:07Z</updated>
450 <published>2024-06-14T12:58:07Z</published>
451 <as:sensitive>false</as:sensitive>
452 <author>
453 <name>https://mastodon.social/users/nicholas_saunders</name>
454 <wf:uri></wf:uri>
455 <uri>https://mastodon.social/users/nicholas_saunders</uri></author>
456 <link rel="self" href="https://mastodon.social/users/nicholas_saunders/statuses/112615088973179734"/>
457 <thr:in-reply-to ref="https://floss.social/users/wschenk/statuses/112615060106830322"/>
458 <content type="text">@wschenk@floss.social @geoglyphentropy@mstdn.social @nus@mstdn.social @DavidKafri@tooot.im @thetechtutor@me.dm After the Goat refused to explain what military action in response to the #alaqsaflood[1] would've been moral he lost all priveleges with me, although he eventually admitted that nothing would meet his standards. Eventually he blocked me, although he seems to have somehow replied to me.
460 I'm inferring that this has something to with the Goat, but have no idea. Guess I'll never know what it was about.
462 [1]: https://mastodon.social/tags/alaqsaflood</content>
463 </entry>|};
465 let apu : As2_vocab.Types.note = load_note "note-OiqQGte9TSY.json" in
466 (match apu.in_reply_to with
467 | [ u ] -> u |> Uri.to_string |> Assrt.equals_string __LOC__ {|https://infosec.exchange/users/womble/statuses/112948594753912420|}
468 | _ -> failwith __LOC__);
469 let ato : Rfc4287.Entry.t = apu |> Ap.Note.to_rfc4287 ~tz ~now in
470 apu.in_reply_to |> List.length |> Assrt.equals_int __LOC__ 1;
471 ato.in_reply_to |> List.length |> Assrt.equals_int __LOC__ (apu.in_reply_to |> List.length);
472 buf |> Buffer.clear;
473 buf |> Xml.to_buf (ato |> Rfc4287.Entry.to_atom ~attr ~base);
474 buf |> Buffer.to_bytes |> Bytes.to_string |> Assrt.equals_string __LOC__ {|<?xml version="1.0"?>
475 <entry xml:lang="und" xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:as="https://www.w3.org/ns/activitystreams" xmlns:wf="urn:ietf:rfc:7033">
476 <id>https://benjojo.co.uk/u/benjojo/h/89L5knBH4TGG4qY4VT</id>
477 <title type="text"></title>
478 <updated>2024-08-12T10:40:45Z</updated>
479 <published>2024-08-12T10:40:45Z</published>
480 <as:sensitive>false</as:sensitive>
481 <author>
482 <name>benjojo</name>
483 <wf:uri>acct:benjojo@benjojo.co.uk</wf:uri>
484 <uri>https://benjojo@benjojo.co.uk/u/benjojo</uri></author>
485 <link rel="self" href="https://benjojo.co.uk/u/benjojo/h/89L5knBH4TGG4qY4VT"/>
486 <thr:in-reply-to ref="https://infosec.exchange/users/womble/statuses/112948594753912420"/>
487 <content type="text">@womble@infosec.exchange It's automatically enabled on most modern android devices and as far as I understand iPhones too. I would assume either the phone that is sending the messages just enabled it seemlessly with w/e API they are using to dispatch messages, or that they are assuming (probably correctly) that whatever carrier side spam filtering exists on the SMS end does not get applied on the RCS end</content>
488 </entry>|};
491 let tc_create_note () =
492 let base = "https://example.org/" |> Uri.of_string in
493 let n = {Ap.Note.empty with
494 id = "note-42" |> Uri.of_string |> Http.reso ~base;
495 attributed_to = "alice" |> Uri.of_string |> Http.reso ~base;
496 to_ = [As2_vocab.Constants.ActivityStreams.public];
497 cc = ["activitypub/subscribers/index.jsa" |> Uri.of_string |> Http.reso ~base]} in
498 let c = n |> Ap.Note.Create.make in
499 c.to_
500 |> List.map Uri.to_string |> Astring.String.concat ~sep:" "
501 |> check string __LOC__ "https://www.w3.org/ns/activitystreams#Public";
502 c.cc
503 |> List.map Uri.to_string |> Astring.String.concat ~sep:" "
504 |> check string __LOC__ "https://example.org/activitypub/subscribers/index.jsa";
505 c.obj.to_
506 |> List.map Uri.to_string |> Astring.String.concat ~sep:" "
507 |> check string __LOC__ "https://www.w3.org/ns/activitystreams#Public";
508 c.obj.cc
509 |> List.map Uri.to_string |> Astring.String.concat ~sep:" "
510 |> check string __LOC__ "https://example.org/activitypub/subscribers/index.jsa";
512 |> As2_vocab.Encode.(create ~lang:As2_vocab.Constants.ActivityStreams.und ~base (note ~base))
513 |> Ezjsonm.value_to_string ~minify:false
514 |> A.equals_string __LOC__ {|{
515 "@context": [
516 "https://www.w3.org/ns/activitystreams",
517 "https://w3id.org/security/v1",
519 "schema": "http://schema.org#",
520 "PropertyValue": "schema:PropertyValue",
521 "value": "schema:value",
522 "@language": "und"
525 "type": "Create",
526 "id": "https://example.org/note-42#Create",
527 "actor": "https://example.org/alice",
528 "to": "https://www.w3.org/ns/activitystreams#Public",
529 "cc": "https://example.org/activitypub/subscribers/index.jsa",
530 "directMessage": false,
531 "object": {
532 "type": "Note",
533 "id": "https://example.org/note-42",
534 "attributedTo": "https://example.org/alice",
535 "to": "https://www.w3.org/ns/activitystreams#Public",
536 "cc": "https://example.org/activitypub/subscribers/index.jsa",
537 "mediaType": "text/html; charset=utf8",
538 "sensitive": false
540 }|};
543 let tc_decode_create_notes () =
544 Logr.info (fun m -> m "%s" __LOC__);
545 let dir = "data/ap/inbox/create/note/" in
546 let l : As2_vocab.Types.note list =
547 dir |> File.fold_dir
548 (fun init f ->
549 let f = dir ^ f in
551 let j = f |> File.in_channel Ezjsonm.from_channel in
552 (match j |> As2_vocab.Activitypub.Decode.obj with
553 | Ok `Update { obj = `Note obj; _ }
554 | Ok `Create { obj = `Note obj; _ } -> obj :: init
555 | _ ->
556 Logr.debug (fun m -> m "%s %s no!" "decode_create_notes" f);
557 init),true
558 with
559 | e ->
560 Logr.debug (fun m -> m "%s %s ouch! %s" "decode_create_notes" f (Printexc.to_string e));
561 init,true)
562 [] in
563 l |> List.length |> Assrt.equals_int __LOC__ 204;
564 let now = ((2024,3,19), ((1,2,3),60*60)) |> Ptime.of_date_time |> Option.value ~default:Ptime.epoch in
565 let tz = Timedesc.Time_zone.make "Europe/Amsterdam" |> Option.value ~default:Timedesc.Time_zone.utc in
566 let l = l |> List.map (Ap.Note.to_rfc4287 ~tz ~now) in
567 l |> List.length |> Assrt.equals_int __LOC__ 204;
568 let l = l |> List.sort Rfc4287.Entry.compare in
569 let l = l |> List.filteri (fun i _ -> i < 2) in
570 let x = l |> Rfc4287.Feed.to_atom
571 ~base:(Uri.of_string "http://example.com/")
572 ~self:(Uri.of_string "seppo.cgi/p-37/")
573 ~prev:None
574 ~next:None
575 ~first:(Uri.of_string "seppo.cgi/p-37/")
576 ~last:(Uri.of_string "seppo.cgi/p-0/")
577 ~title:"ti"
578 ~updated:(Rfc3339.T "2024-06-19 16:15:14+02:00")
579 ~lang:(Rfc4287.Rfc4646 "de")
580 ~author:{Rfc4287.Person.empty with
581 name = "Alice";
582 uri = Some ("alice@example.com" |> Uri.of_string)} in
583 let fn = "./tmp/as2_vocab_text.dat" in
584 (try fn |> Unix.unlink with _ -> ());
585 fn |> File.out_channel_replace (x |> Xml.to_chan ~xsl:(Rfc4287.xsl "posts.xsl" fn));
586 (match fn |> File.cat with
587 | Error _ -> failwith __LOC__
588 | Ok s -> s)
589 |> Assrt.equals_string __LOC__ {|<?xml version="1.0"?>
590 <?xml-stylesheet type="text/xsl" href="../../themes/current/posts.xsl"?>
591 <feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:wf="urn:ietf:rfc:7033" xmlns:as="https://www.w3.org/ns/activitystreams" xml:lang="de" xml:base="http://example.com/">
592 <id>http://example.com/seppo.cgi/p-37/</id>
593 <title type="text">ti</title>
594 <updated>2024-06-19 16:15:14+02:00</updated>
595 <generator uri="Seppo.mro.name">Seppo - Personal Social Web</generator>
596 <link rel="self" href="seppo.cgi/p-37/" title="38"/>
597 <link rel="first" href="seppo.cgi/p-37/" title="last"/>
598 <link rel="last" href="seppo.cgi/p-0/" title="1"/>
599 <entry xml:lang="en">
600 <id>https://mastodon.social/users/nicholas_saunders/statuses/112608234771751001</id>
601 <title type="text"></title>
602 <updated>2024-06-13T09:55:01+02:00</updated>
603 <published>2024-06-13T09:55:01+02:00</published>
604 <as:sensitive>false</as:sensitive>
605 <author>
606 <name>https://mastodon.social/users/nicholas_saunders</name>
607 <wf:uri></wf:uri>
608 <uri>https://mastodon.social/users/nicholas_saunders</uri></author>
609 <link rel="self" href="https://mastodon.social/users/nicholas_saunders/statuses/112608234771751001"/>
610 <thr:in-reply-to ref="https://infosec.exchange/users/SpaceLifeForm/statuses/112608204971110992"/>
611 <content type="text">@SpaceLifeForm@infosec.exchange @RickiTarr@beige.party I'll be here all day @SpaceLifeForm@infosec.exchange :)
613 (not really.)</content>
614 </entry>
615 <entry xml:lang="en">
616 <id>https://mastodon.social/users/nicholas_saunders/statuses/112606941257663038</id>
617 <title type="text"></title>
618 <updated>2024-06-13T04:26:03+02:00</updated>
619 <published>2024-06-13T04:26:03+02:00</published>
620 <as:sensitive>false</as:sensitive>
621 <author>
622 <name>https://mastodon.social/users/nicholas_saunders</name>
623 <wf:uri></wf:uri>
624 <uri>https://mastodon.social/users/nicholas_saunders</uri></author>
625 <link rel="self" href="https://mastodon.social/users/nicholas_saunders/statuses/112606941257663038"/>
626 <thr:in-reply-to ref="https://mastodon.social/users/thejapantimes/statuses/112606642206596379"/>
627 <content type="text">@thejapantimes@mastodon.social so then #hamas[1] didn't accept the offer, but rather seeks different terms.
629 [1]: https://mastodon.social/tags/hamas</content>
630 </entry>
631 </feed>|}
633 let () =
635 "seppo_suite" [
636 __FILE__ , [
637 "set_up", `Quick, set_up;
638 "tc_follower_state", `Quick, tc_follower_state;
639 "tc_inbox_follow", `Quick, tc_inbox_follow;
640 "tc_inbox_unfollow", `Quick, tc_inbox_unfollow;
641 "tc_followers_page_json_raw",`Quick, tc_followers_page_json_raw;
642 "tc_followers_page_json", `Quick, tc_followers_page_json;
643 "tc_followers_json", `Quick, tc_followers_json;
644 "tc_followers_cdb_json", `Quick, tc_followers_cdb_json;
645 "Note.tc_of_rfc4287", `Quick, Note.tc_of_rfc4287;
646 "Note.tc_create_note_json", `Quick, Note.tc_create_note_json;
647 "Note.tc_diluviate", `Quick, Note.tc_diluviate;
648 "Note.tc_note_to_plain", `Quick, Note.tc_note_to_plain;
649 "tc_create_follow_json", `Quick, tc_create_follow_json;
650 "tc_reject_json", `Quick, tc_reject_json;
651 "tc_subscribed", `Quick, tc_subscribed;
652 "tc_to_rfc4287_0", `Quick, tc_to_rfc4287_0;
653 "tc_to_rfc4287_loaded", `Quick, tc_to_rfc4287_loaded;
654 "tc_create_note", `Quick, tc_create_note;
655 "tc_decode_create_notes", `Quick, tc_decode_create_notes;
658 assert true