as2_test.ml 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. (*
  2. * _ _ ____ _
  3. * _| || |_/ ___| ___ _ __ _ __ ___ | |
  4. * |_ .. _\___ \ / _ \ '_ \| '_ \ / _ \| |
  5. * |_ _|___) | __/ |_) | |_) | (_) |_|
  6. * |_||_| |____/ \___| .__/| .__/ \___/(_)
  7. * |_| |_|
  8. *
  9. * Personal Social Web.
  10. *
  11. * as2_test.ml
  12. *
  13. * Copyright (C) The #Seppo contributors. All rights reserved.
  14. *
  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.
  19. *
  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.
  24. *
  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/>.
  27. *)
  28. open Seppo_lib
  29. let test_digest_sha256 () =
  30. Logr.debug (fun m -> m "as2_test.test_digest_sha256");
  31. let (`Hex h) =
  32. "" |> Cstruct.of_string |> Mirage_crypto.Hash.SHA256.digest
  33. |> Hex.of_cstruct
  34. in
  35. (* https://de.wikipedia.org/wiki/SHA-2 *)
  36. h
  37. |> Assrt.equals_string __LOC__
  38. "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
  39. "" |> Cstruct.of_string |> Mirage_crypto.Hash.SHA256.digest
  40. |> Cstruct.to_string |> Base64.encode_exn
  41. (* printf "%s" "" | openssl dgst -sha256 -binary | base64 *)
  42. |> Assrt.equals_string __LOC__
  43. "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="
  44. let test_digst () =
  45. Logr.debug (fun m -> m "as2_test.test_digst");
  46. "" |> Ap.Activity.digest_base64
  47. |> Assrt.equals_string __LOC__
  48. "SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=";
  49. assert true
  50. let test_person () =
  51. Logr.debug (fun m -> m "as2_test.test_person");
  52. let pubdate = Ptime_clock.now ()
  53. and pem = "foo"
  54. and pro = ({
  55. title = "Sepp"; (* similar atom:subtitle *)
  56. bio = "sum"; (* similar atom:description *)
  57. language = Rfc4287.Rfc4646 "de";
  58. timezone = Timedesc.Time_zone.utc;
  59. posts_per_page = 50;
  60. } : Cfg.Profile.t)
  61. and uid = "sepp"
  62. and base = Uri.of_string "https://example.com/subb/" in
  63. let Rfc4287.Rfc4646 context = pro.language in
  64. let context = Some context in
  65. let p = Ap.Person.prsn pubdate (pem, (pro, (Auth.Uid uid, base))) in
  66. p |> As2_vocab.Encode.person ~context ~base
  67. |> Ezjsonm.value_to_string ~minify:false
  68. |> Assrt.equals_string __LOC__ {|{
  69. "@context": [
  70. "https://www.w3.org/ns/activitystreams",
  71. "https://w3id.org/security/v1",
  72. {
  73. "schema": "http://schema.org#",
  74. "PropertyValue": "schema:PropertyValue",
  75. "value": "schema:value",
  76. "@language": "de"
  77. }
  78. ],
  79. "type": "Person",
  80. "id": "https://example.com/subb/activitypub/actor.jsa",
  81. "inbox": "https://example.com/subb/seppo.cgi/activitypub/inbox.jsa",
  82. "outbox": "https://example.com/subb/activitypub/outbox/index.jsa",
  83. "followers": "https://example.com/subb/activitypub/notify/index.jsa",
  84. "following": "https://example.com/subb/activitypub/subscribed/index.jsa",
  85. "name": "Sepp",
  86. "url": [
  87. "https://example.com/subb/"
  88. ],
  89. "preferredUsername": "sepp",
  90. "summary": "sum",
  91. "summaryMap": {
  92. "de": "sum"
  93. },
  94. "publicKey": {
  95. "@context": [
  96. {
  97. "@language": null
  98. }
  99. ],
  100. "id": "https://example.com/subb/activitypub/actor.jsa#main-key",
  101. "owner": "https://example.com/subb/activitypub/actor.jsa",
  102. "publicKeyPem": "foo",
  103. "signatureAlgorithm": "https://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
  104. },
  105. "manuallyApprovesFollowers": false,
  106. "discoverable": true,
  107. "generator": {
  108. "type": "Link",
  109. "href": "https://seppo.social",
  110. "name": "Seppo - Personal Social Web"
  111. },
  112. "attachment": [],
  113. "icon": {
  114. "type": "Image",
  115. "url": "https://example.com/subb/me-avatar.jpg"
  116. },
  117. "image": {
  118. "type": "Image",
  119. "url": "https://example.com/subb/me-banner.jpg"
  120. }
  121. }|};
  122. let _tos0 = Ezxmlm.to_string in
  123. let tos ?(decl = false) ?(indent = None) doc =
  124. let buf = Buffer.create 512 in
  125. let o = Xmlm.make_output ~decl (`Buffer buf) ~nl:true ~indent in
  126. let id x = x in
  127. Xmlm.output_doc_tree id o (None, doc);
  128. Buffer.contents buf
  129. in
  130. p
  131. |> Ap.Person.Rdf.encode
  132. ~token:(Some "foo")
  133. ~notify:(Some As2.No_p_yes.No)
  134. ~subscribed:(Some As2.No_p_yes.Pending)
  135. ~blocked:(Some As2.No_p_yes.Yes)
  136. ~context ~base
  137. |> tos
  138. |> Assrt.equals_string __LOC__ {|<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xml:base="https://example.com/subb/">
  139. <rdf:Description xmlns:seppo="http://seppo.social/2023/ns#" rdf:about="">
  140. <seppo:token>foo</seppo:token>
  141. <seppo:notify>no</seppo:notify>
  142. <seppo:subscribed>pending</seppo:subscribed>
  143. <seppo:blocked>yes</seppo:blocked>
  144. </rdf:Description>
  145. <as:Person xmlns:as="https://www.w3.org/ns/activitystreams#" xmlns:ldp="http://www.w3.org/ns/ldp#" xmlns:schema="http://schema.org#" xmlns:toot="http://joinmastodon.org/ns#" rdf:about="" xml:lang="de">
  146. <as:id rdf:resource="https://example.com/subb/activitypub/actor.jsa"/>
  147. <as:preferredUsername>sepp</as:preferredUsername>
  148. <as:manuallyApprovesFollowers rdf:datatype="http://www.w3.org/2001/XMLSchema#boolean">false</as:manuallyApprovesFollowers>
  149. <toot:discoverable rdf:datatype="http://www.w3.org/2001/XMLSchema#boolean">true</toot:discoverable>
  150. <as:generator/>
  151. <as:name>Sepp</as:name>
  152. <as:url rdf:resource="https://example.com/subb/"/>
  153. <as:summary xml:lang="de">sum</as:summary>
  154. <as:summary>sum</as:summary>
  155. <as:icon>
  156. <as:Image>
  157. <as:url rdf:resource="https://example.com/subb/me-avatar.jpg"/></as:Image></as:icon>
  158. <as:image>
  159. <as:Image>
  160. <as:url rdf:resource="https://example.com/subb/me-banner.jpg"/></as:Image></as:image>
  161. <as:following rdf:resource="https://example.com/subb/activitypub/subscribed/index.jsa"/>
  162. <as:followers rdf:resource="https://example.com/subb/activitypub/notify/index.jsa"/>
  163. <ldp:inbox rdf:resource="https://example.com/subb/seppo.cgi/activitypub/inbox.jsa"/>
  164. <as:outbox rdf:resource="https://example.com/subb/activitypub/outbox/index.jsa"/>
  165. </as:Person></rdf:RDF>
  166. |};
  167. ()
  168. let test_actor () =
  169. Logr.debug (fun m -> m "as2_test.test_actor");
  170. Logr.info (fun m -> m "test_actor");
  171. ("data/ap/actor/friendica.0.json"
  172. |> Ap.Person.from_file |> Result.get_ok).inbox
  173. |> Uri.to_string
  174. |> Assrt.equals_string __LOC__ "https://pirati.ca/inbox/heluecht";
  175. ("data/ap/actor/mastodon.2.json"
  176. |> Ap.Person.from_file |> Result.get_ok).inbox
  177. |> Uri.to_string
  178. |> Assrt.equals_string __LOC__ "https://digitalcourage.social/users/mro/inbox";
  179. ("data/ap/actor/gnusocial.2.json"
  180. |> Ap.Person.from_file |> Result.get_ok).inbox
  181. |> Uri.to_string
  182. |> Assrt.equals_string __LOC__ "https://social.hackersatporto.com/user/1/inbox.json";
  183. ("data/ap/actor/pleroma.0.json"
  184. |> Ap.Person.from_file |> Result.get_ok).inbox
  185. |> Uri.to_string
  186. |> Assrt.equals_string __LOC__ "https://pleroma.tilde.zone/users/mro/inbox";
  187. assert true
  188. let test_examine_response () =
  189. {|{"error":"Unable to fetch key JSON at https://example.com/activitypub/#main-key"}|}
  190. |> As2.examine_response
  191. |> Result.get_error
  192. |> Assrt.equals_string __LOC__ "Unable to fetch key JSON at https://example.com/activitypub/#main-key";
  193. assert true
  194. module Note = struct
  195. let test_decode () =
  196. Logr.info (fun m -> m "%s.%s" "As2_test.Note" "decode");
  197. let j = File.in_channel "data/ap/note/mastodon.json" Ezjsonm.from_channel in
  198. let n = j |> As2_vocab.Decode.note |> Result.get_ok in
  199. n.id |> Uri.to_string |> Assrt.equals_string __LOC__ "https://digitalcourage.social/users/mro/statuses/111403080326863922";
  200. match n.in_reply_to with
  201. | [u] -> u |> Uri.to_string |> Assrt.equals_string __LOC__ "https://chaos.social/users/qyliss/statuses/111403054651938519"
  202. | _ -> failwith "ouch"
  203. let test_of_rfc4287 () =
  204. let open Rfc4287 in
  205. let e : Entry.t = {
  206. id = "" |> Uri.of_string;
  207. in_reply_to = [];
  208. lang = Rfc4646 "nl";
  209. author = Uri.empty;
  210. title = "Title";
  211. published = Rfc3339.T "2023-10-24T11:12:13+02:00";
  212. updated = Rfc3339.T "2023-10-24T11:12:13+02:00";
  213. links = [Link.make ("https://Seppo.Social/demo" |> Uri.of_string) ];
  214. categories = [];
  215. content = "string";
  216. } in
  217. e.title |> Assrt.equals_string __LOC__ "Title";
  218. let n = e |> Ap.Note.of_rfc4287 in
  219. n.summary |> Option.value ~default:"-" |> Assrt.equals_string __LOC__ "Title";
  220. n.url |> List.hd |> Uri.to_string |> Assrt.equals_string __LOC__ "https://seppo.social/demo"
  221. let test_diluviate () =
  222. let n : As2_vocab.Types.note = {
  223. id = Uri.empty;
  224. actor = Uri.empty;
  225. attachment = [];
  226. cc = [];
  227. in_reply_to= [];
  228. media_type = None; (* https://www.w3.org/TR/activitystreams-vocabulary/#dfn-mediatype *)
  229. content = "Content";
  230. content_map= ["de","Inhalt"];
  231. published = None;
  232. sensitive = false;
  233. source = None;
  234. summary = Some "Summary";
  235. summary_map= [];
  236. tags = [];
  237. to_ = [];
  238. url = ["https://Seppo.Social/demo" |> Uri.of_string];
  239. (*raw: jsonm;*)
  240. } in
  241. let base = Uri.empty in
  242. let minify = false in
  243. n
  244. |> Ap.Note.diluviate
  245. |> As2_vocab.Encode.note ~base |> Ezjsonm.value_to_string ~minify |> Assrt.equals_string __LOC__ {|{
  246. "type": "Note",
  247. "id": "",
  248. "actor": "",
  249. "to": [],
  250. "cc": [],
  251. "content": "Summary<br/>\n<a href='https://seppo.social/demo'>https://seppo.social/demo</a><br/>\n<br/>\nContent",
  252. "contentMap": {
  253. "de": "Inhalt"
  254. },
  255. "sensitive": false,
  256. "tags": [],
  257. "url": [
  258. ""
  259. ]
  260. }|};
  261. assert true
  262. end
  263. let () =
  264. Unix.chdir "../../../test/";
  265. test_digest_sha256 ();
  266. test_digst ();
  267. test_person ();
  268. test_actor ();
  269. test_examine_response ();
  270. Note.test_decode ();
  271. Note.test_of_rfc4287 ();
  272. Note.test_diluviate ();
  273. assert true