t_as2_vocab.ml 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. open Alcotest
  2. open Seppo_lib
  3. module A = Assrt
  4. let set_up = "set_up", `Quick, (fun () ->
  5. Unix.chdir "../../../test/"
  6. )
  7. let tc_err = "tc_err", `Quick, (fun () ->
  8. match {|[]|}
  9. |> Ezjsonm.from_string
  10. |> As2_vocab.Activitypub.Decode.obj with
  11. | Error (e : Decoders_ezjsonm.Decode.error) ->
  12. e |> Format.asprintf "%a" Decoders_ezjsonm.Decode.pp_error
  13. |> check string __LOC__ {|I tried the following decoders but they all failed:
  14. "core_obj" decoder: Expected a string, but got []
  15. "core_obj event" decoder:
  16. Expected an object with an attribute "type", but got [] |};
  17. e |> Decoders_ezjsonm.Decode.string_of_error
  18. |> check string __LOC__ {|I tried the following decoders but they all failed:
  19. "core_obj" decoder: Expected a string, but got []
  20. "core_obj event" decoder:
  21. Expected an object with an attribute "type", but got [] |};
  22. | _ -> failwith __LOC__
  23. (* diaspora profile json e.g.
  24. https://pod.diaspora.software/u/hq
  25. https://pod.diaspora.software/people/7bca7c80311b01332d046c626dd55703
  26. *)
  27. )
  28. let tc_person = "tc_person", `Quick, (fun () ->
  29. let empty : As2_vocab.Types.person = {
  30. id = Uri.empty;
  31. inbox = Uri.empty;
  32. outbox = Uri.empty;
  33. followers = None;
  34. following = None;
  35. attachment = [];
  36. discoverable = false;
  37. generator = None;
  38. icon = [];
  39. image = None;
  40. manually_approves_followers = false;
  41. name = None;
  42. name_map = [];
  43. preferred_username = None;
  44. preferred_username_map = [];
  45. public_key = {
  46. id = Uri.empty;
  47. owner = None;
  48. pem = "";
  49. signatureAlgorithm = None;
  50. };
  51. published = Some Ptime.epoch;
  52. summary = None;
  53. summary_map = [];
  54. url = [];
  55. } in
  56. let base = (Uri.of_string "https://example.com/su/") in
  57. let lang = As2_vocab.Constants.ActivityStreams.und in
  58. {empty with id=Uri.make ~path:"id/" ()}
  59. |> As2_vocab.Encode.person ~lang ~base
  60. |> Ezjsonm.value_to_string ~minify:false
  61. |> check string __LOC__ {|{
  62. "@context": [
  63. "https://www.w3.org/ns/activitystreams",
  64. "https://w3id.org/security/v1",
  65. {
  66. "schema": "http://schema.org#",
  67. "PropertyValue": "schema:PropertyValue",
  68. "value": "schema:value",
  69. "@language": "und"
  70. }
  71. ],
  72. "type": "Person",
  73. "id": "https://example.com/su/id/",
  74. "inbox": "https://example.com/su/",
  75. "outbox": "https://example.com/su/",
  76. "publicKey": {
  77. "@context": [
  78. {
  79. "@language": null
  80. }
  81. ],
  82. "id": "https://example.com/su/",
  83. "publicKeyPem": ""
  84. },
  85. "published": "1970-01-01T00:00:00Z",
  86. "manuallyApprovesFollowers": false,
  87. "discoverable": false
  88. }|};
  89. let p = {|{
  90. "@context": [
  91. "https://www.w3.org/ns/activitystreams",
  92. "https://w3id.org/security/v1",
  93. {
  94. "schema": "http://schema.org#",
  95. "PropertyValue": "schema:PropertyValue",
  96. "value": "schema:value",
  97. "@language": "und"
  98. }
  99. ],
  100. "type": "Person",
  101. "id": "https://example.com/su/id/",
  102. "inbox": "https://example.com/su/",
  103. "outbox": "https://example.com/su/",
  104. "publicKey": {
  105. "id": "https://example.com/su/",
  106. "owner": "https://example.com/su/",
  107. "publicKeyPem": ""
  108. },
  109. "published": "1970-01-01T00:00:00Z",
  110. "manuallyApprovesFollowers": false,
  111. "discoverable": false,
  112. "attachment": []
  113. }|}
  114. |> Ezjsonm.value_from_string
  115. |> As2_vocab.Decode.person
  116. |> Result.get_ok in
  117. p.id
  118. |> Uri.to_string
  119. |> check string __LOC__ "https://example.com/su/id/"
  120. )
  121. let tc_actor_3rd = "tc_actor_3rd", `Quick, (fun () ->
  122. Logr.info (fun m -> m "%s.%s" "As2_vocab" "actor_3rd");
  123. let ok loc fn na =
  124. let j = fn |> File.in_channel Ezjsonm.from_channel in
  125. let p = j |> As2_vocab.Decode.person |> Result.get_ok in
  126. p.name |> Option.get |> check string loc na
  127. and oki loc fn id =
  128. let j = fn |> File.in_channel Ezjsonm.from_channel in
  129. let p = j |> As2_vocab.Decode.person |> Result.get_ok in
  130. p.id |> Uri.to_string |> check string loc (id)
  131. and err loc fn e =
  132. let j = fn |> File.in_channel Ezjsonm.from_channel in
  133. j |> As2_vocab.Decode.person |> Result.get_error
  134. |> Decoders_ezjsonm.Decode.string_of_error
  135. |> check string loc e
  136. and fal _loc fn =
  137. let j = fn |> File.in_channel Ezjsonm.from_channel in
  138. assert (j |> As2_vocab.Decode.person |> Result.is_error)
  139. in
  140. (* ok __LOC__ "data/ap/actor/lemmy.0.json" ""; *)
  141. ok __LOC__ "data/ap/actor/akkoma.0.json" "Sean Tilley";
  142. ok __LOC__ "data/ap/actor/akkoma.1.json" "Kinetix";
  143. ok __LOC__ "data/ap/actor/bonfire.0.json" "stpaultim";
  144. ok __LOC__ "data/ap/actor/bridgy.0.json" "Tantek Çelik";
  145. ok __LOC__ "data/ap/actor/friendica.0.json" "Michael Vogel";
  146. ok __LOC__ "data/ap/actor/gnusocial.0.json" "Diogo Peralta Cordeiro";
  147. ok __LOC__ "data/ap/actor/gnusocial.1.json" "admin de gnusocial.net";
  148. ok __LOC__ "data/ap/actor/gnusocial.2.json" "diogo";
  149. ok __LOC__ "data/ap/actor/gotosocial.0.json" "Gerben";
  150. ok __LOC__ "data/ap/actor/gotosocial.1.json" "Gerben";
  151. oki __LOC__ "data/ap/actor/gotosocial.1b.json" "https://social.nlnet.nl/users/gerben";
  152. ok __LOC__ "data/ap/actor/honk.0.json" "boyter";
  153. ok __LOC__ "data/ap/actor/mastodon.0.json" "Yet Another #Seppo! 🌻";
  154. ok __LOC__ "data/ap/actor/mastodon.1.json" "#Seppo";
  155. ok __LOC__ "data/ap/actor/mastodon.2.json" "Marcus Rohrmoser 🌍";
  156. ok __LOC__ "data/ap/actor/mini.0.json" "Yet Another #Seppo! 🌻";
  157. ok __LOC__ "data/ap/actor/misskey.0.json" "しゅいろ";
  158. ok __LOC__ "data/ap/actor/peertube.0.json" "edps";
  159. ok __LOC__ "data/ap/actor/peertube.1.json" {|Q3 Marcus|};
  160. ok __LOC__ "data/ap/actor/peertube.2.json" {|edps|};
  161. ok __LOC__ "data/ap/actor/peertube.3.json" {|Framasoft|};
  162. ok __LOC__ "data/ap/actor/pixelfed.0.json" {|#Seppo|};
  163. ok __LOC__ "data/ap/actor/pleroma.0.json" "@fediverse@mro.name";
  164. ok __LOC__ "data/ap/actor/smithereen.0.json" {|Григорий Клюшников|};
  165. ok __LOC__ "data/ap/actor/snac.0.json" {|The Real Grunfink|};
  166. ok __LOC__ "data/ap/actor/threads.0.json" "Ben Savage";
  167. ok __LOC__ "data/ap/actor/tootik.0.json" {|EOIN GAIRLEOG|};
  168. ok __LOC__ "data/ap/actor/zap.0.json" "mike";
  169. ok __LOC__ "data/ap/profile/@actapopuli@fediverse.blog.json" "actapopuli";
  170. ok __LOC__ "data/ap/profile/@administrator@gnusocial.net.json" {|admin de gnusocial.net|};
  171. ok __LOC__ "data/ap/profile/@dansup@pixelfed.social.json" {|dansup|};
  172. ok __LOC__ "data/ap/profile/@gargron@mastodon.social.json" {|Eugen Rochko|};
  173. ok __LOC__ "data/ap/profile/@kainoa@calckey.social.json" {|Kainoa |};
  174. ok __LOC__ "data/ap/profile/@karolat@stereophonic.space.json" {|karolat|};
  175. ok __LOC__ "data/ap/profile/@manton@manton.org.json" {|Manton Reece|};
  176. ok __LOC__ "data/ap/profile/@matt@write.as.json" {|Matt|};
  177. ok __LOC__ "data/ap/profile/@mike@macgirvin.com.json" {|Mike Macgirvin|};
  178. ok __LOC__ "data/ap/profile/@peertube@framapiaf.org.json" {|PeerTube|};
  179. ok __LOC__ "data/ap/profile/@syuilo@misskey.io.json" {|:peroro_sama:しゅいろ:peroro_sama:|};
  180. ok __LOC__ "data/ap/profile/@tobias@friendi.ca.json" {|Tobias|};
  181. (*
  182. err __LOC__ "data/ap/actor/gotosocial.1b.json" {|Expected an object with an attribute "inbox", but got
  183. {"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1"],"id":"https://social.nlnet.nl/users/gerben","preferredUsername":"gerben","publicKey":{"id":"https://social.nlnet.nl/users/gerben/main-key","owner":"https://social.nlnet.nl/users/gerben","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA35UDYAz/pgrIi4O1UDf9\n9W1RnEPVtj730gfujlIQtzWIsBs/8n6zQbK0QBtRSYKhwzkUDMpNUh+Mm/CONjKI\nSwps6mEknlmi3VbyviGYs7LX7SuJAfLIl1hT2eftXQLObKgzkbUcVbS0zwNlBeIX\nPJpHyAVphuI4a02Be458g3y3nSK7Imb4dN2GUhLX2FDOZ6s/EC9HcglZiiz+9hI2\n0Rb4tXjsjbBXe6ofowdXDPmD20Al3v816m4kYfgGmLK86ItFGj8u+hoK8tcC0yWk\ni7KUL3bg9iqp7wBhkjTZ/vVVIf7RG8q2gPRe1BVsi+GBDmV7dmupgicT6UbmAi5h\nTwIDAQAB\n-----END PUBLIC KEY-----\n"},"type":"Person"}|};
  184. *)
  185. err __LOC__ "data/ap/actor/friendica.1.json" {|I tried the following decoders but they all failed:
  186. "type" decoder:
  187. in field "type":
  188. expected Person (received Organization), but got "Organization"
  189. "type" decoder:
  190. in field "type":
  191. expected Service (received Organization), but got "Organization" |};
  192. err __LOC__ "data/ap/profile/@lemmy_support@lemmy.ml.json" {|I tried the following decoders but they all failed:
  193. "type" decoder:
  194. in field "type": expected Person (received Group), but got "Group"
  195. "type" decoder:
  196. in field "type": expected Service (received Group), but got "Group" |};
  197. err __LOC__ "data/ap/actor/mobilizon.0.json" {|I tried the following decoders but they all failed:
  198. "type" decoder:
  199. in field "type": expected Person (received Group), but got "Group"
  200. "type" decoder:
  201. in field "type": expected Service (received Group), but got "Group" |};
  202. fal __LOC__ "data/ap/actor/diaspora.0.json";
  203. ()
  204. (*
  205. ok __LOC__ "data/ap/actor/natur.0.json" "しゅいろ";
  206. ok __LOC__ "data/ap/actor/natur.1.json" "しゅいろ";
  207. ok __LOC__ "data/ap/actor/sharkey.0.json" {||};
  208. ok __LOC__ "data/ap/actor/sharkey.1.json" {||};
  209. ok __LOC__ "data/ap/profile/@framasoft@mobilizon.fr.json" {||};
  210. ok __LOC__ "data/ap/profile/@Greensky@open.audio.json" {||};
  211. *)
  212. )
  213. let tc_actor_decode = "tc_actor_decode", `Quick, (fun () ->
  214. let fn = "data/ap/actor/peertube.3.json" in
  215. let j = fn |> File.in_channel Ezjsonm.from_channel in
  216. let p = j |> As2_vocab.Decode.person |> Result.get_ok in
  217. p.name |> Option.get |> check string __LOC__ "Framasoft";
  218. p.icon |> List.length |> check int __LOC__ 2;
  219. ()
  220. )
  221. let tc_actor_encode = "tc_actor_encode", `Quick, (fun () ->
  222. let base = Uri.empty
  223. and lang = None
  224. and minify = false
  225. in
  226. let s = "data/ap/actor/peertube.3.json"
  227. |> File.in_channel Ezjsonm.from_channel
  228. |> As2_vocab.Decode.person |> Result.get_ok
  229. |> As2_vocab.Encode.person ~base ~lang
  230. |> Ezjsonm.value_to_string ~minify in
  231. s |>
  232. check string
  233. (*
  234. check string
  235. *)
  236. __LOC__ {|{
  237. "type": "Person",
  238. "id": "https://framatube.org/accounts/framasoft",
  239. "inbox": "https://framatube.org/accounts/framasoft/inbox",
  240. "outbox": "https://framatube.org/accounts/framasoft/outbox",
  241. "followers": "https://framatube.org/accounts/framasoft/followers",
  242. "following": "https://framatube.org/accounts/framasoft/following",
  243. "name": "Framasoft",
  244. "url": "https://framatube.org/accounts/framasoft",
  245. "preferredUsername": "framasoft",
  246. "publicKey": {
  247. "@context": [
  248. {
  249. "@language": null
  250. }
  251. ],
  252. "id": "https://framatube.org/accounts/framasoft#main-key",
  253. "owner": "https://framatube.org/accounts/framasoft",
  254. "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuRh3frgIg866D0y0FThp\nSUkJImMcHGkUvpYQYv2iUgarZZtEbwT8PfQf0bJazy+cP8KqQmMDf5PBhT7dfdny\nf/GKGMw9Olc+QISeKDj3sqZ3Csrm4KV4avMGCfth6eSU7LozojeSGCXdUFz/8UgE\nfhV4mJjEX/FbwRYoKlagv5rY9mkX5XomzZU+z9j6ZVXyofwOwJvmI1hq0SYDv2bc\neB/RgIh/H0nyMtF8o+0CT42FNEET9j9m1BKOBtPzwZHmitKRkEmui5cK256s1laB\nT61KHpcD9gQKkQ+I3sFEzCBUJYfVo6fUe+GehBZuAfq4qDhd15SfE4K9veDscDFI\nTwIDAQAB\n-----END PUBLIC KEY-----"
  255. },
  256. "published": "2018-03-01T15:16:17Z",
  257. "manuallyApprovesFollowers": false,
  258. "discoverable": false,
  259. "icon": [
  260. {
  261. "type": "Image",
  262. "url": "https://framatube.org/lazy-static/avatars/1dbda4f0-1f7f-40f2-b962-85fd0c144661.png"
  263. },
  264. {
  265. "type": "Image",
  266. "url": "https://framatube.org/lazy-static/avatars/f73876f5-1d45-4f8a-942a-d3d5d5ac5dc1.png"
  267. }
  268. ]
  269. }|}
  270. )
  271. let tc_actor_encode_issue35 = "tc_actor_encode", `Quick, (fun () ->
  272. let base = Uri.empty
  273. and url = [ "https://example.com" |> Uri.of_string ]
  274. in
  275. let (<:) = function
  276. | (_, None) -> fun _ -> []
  277. | (field, Some vl) -> fun ty -> [field, ty vl] in
  278. let (@?.) field vl = (field, match vl with | [] -> None | l -> Some l) in
  279. let list (a : 'a Decoders_ezjsonm.Encode.encoder) (b : 'a list) : Ezjsonm.value = match b with
  280. | [] -> Decoders_ezjsonm.Encode.null (* none => omit *)
  281. | [b] -> Decoders_ezjsonm.Encode.encode_value a b (* single => value *)
  282. | b -> Decoders_ezjsonm.Encode.list a b in
  283. let uri ~base u = u |> Uri.resolve "https" base |> Uri.to_string |> Decoders_ezjsonm.Encode.string in
  284. (match
  285. "url" @?. url <: list (uri ~base);
  286. with
  287. | [("url",j)] -> j
  288. |> Ezjsonm.value_to_string
  289. |> check string __LOC__ {|"https://example.com"|}
  290. | _ -> fail __LOC__
  291. ))
  292. let tc_note_decode = "tc_note_dcode", `Quick, (fun () ->
  293. let j = "data/ap/note/mastodon.json" |> File.in_channel Ezjsonm.from_channel in
  294. let n = j |> As2_vocab.Decode.note |> Result.get_ok in
  295. n.id |> Uri.to_string |> check string __LOC__ "https://digitalcourage.social/users/mro/statuses/111403080326863922";
  296. (match n.in_reply_to with
  297. | [u] -> u |> Uri.to_string |> check string __LOC__ "https://chaos.social/users/qyliss/statuses/111403054651938519"
  298. | _ -> failwith "none");
  299. let n = "data/ap/inbox/create/note/akkoma.json" |> File.in_channel Ezjsonm.from_channel
  300. |> As2_vocab.Decode.(create note) |> Result.get_ok in
  301. let _,co = n.obj.content_map |> List.hd in
  302. co
  303. |> Assrt.equals_string __LOC__
  304. {|<p>Oh yeah! Also: trying to flip the pencil over to use an eraser doesn’t work. You have to select an eraser first. </p><p>What the fuck? My shitty Wacom Bamboo tablet from ten years ago can do that!</p>|}
  305. )
  306. let tc_create_article_decode = "tc_create_article_decode", `Quick, (fun () ->
  307. "data/ap/inbox/create/article/friendica.json"
  308. |> File.in_channel Ezjsonm.from_channel
  309. |> As2_vocab.Decode.(create note) |> Result.get_error
  310. |> Decoders_ezjsonm.Decode.string_of_error
  311. |> check string __LOC__ "in field \"object\":\n in field \"type\": expected Note (received Article), but got \"Article\""
  312. )
  313. let tc_reject_decode = "tc_reject_decode", `Quick, (fun () ->
  314. Logr.info (fun m -> m "%s.%s" "As2_vocab" "reject_decode");
  315. let j = "data/ap/inbox/reject/follow/2024-08-17-124924-steve.json" |> File.in_channel Ezjsonm.from_channel in
  316. let n = j |> As2_vocab.Decode.(reject follow) |> Result.get_ok in
  317. n.id
  318. |> Uri.to_string
  319. |> check string __LOC__ {|https://social.technoetic.com/users/steve#rejects/follows/|};
  320. n.obj.id
  321. |> Uri.to_string
  322. |> check string __LOC__ {|https://social.technoetic.com/users/steve#subscribe|}
  323. )
  324. let tc_webfinger_sunshine = "tc_webfinger_sunshine", `Quick, (fun () ->
  325. let q = "data/webfinger/mini.json" |> File.in_channel Ezjsonm.from_channel
  326. |> As2_vocab.Activitypub.Decode.Webfinger.query_result
  327. |> Result.get_ok in
  328. q.subject |> check string __LOC__ "acct:ursi@example.com";
  329. q.links |> List.length |> check int __LOC__ 3;
  330. let q = "data/webfinger/zap.json" |> File.in_channel Ezjsonm.from_channel
  331. |> As2_vocab.Activitypub.Decode.Webfinger.query_result
  332. |> Result.get_ok in
  333. q.subject |> check string __LOC__ "acct:mike@macgirvin.com";
  334. q.links |> List.length |> check int __LOC__ 3;
  335. let q = "data/webfinger/atom.json" |> File.in_channel Ezjsonm.from_channel
  336. |> As2_vocab.Activitypub.Decode.Webfinger.query_result
  337. |> Result.get_ok in
  338. q.subject |> check string __LOC__ "acct:ursi@example.com";
  339. q.links |> List.length |> check int __LOC__ 3
  340. )
  341. let tc_profile_sunshine = "tc_profile_sunshine", `Quick, (fun () ->
  342. let q = "data/ap/actor/mini.0.json" |> File.in_channel Ezjsonm.from_channel
  343. |> As2_vocab.Activitypub.Decode.person
  344. |> Result.get_ok in
  345. q.name |> Option.get |> check string __LOC__ "Yet Another #Seppo! 🌻";
  346. q.preferred_username |> Option.get |> check string __LOC__ "ursi";
  347. q.attachment |> List.length |> check int "" 0;
  348. let q = "data/ap/actor/mastodon.0.json" |> File.in_channel Ezjsonm.from_channel
  349. |> As2_vocab.Activitypub.Decode.person
  350. |> Result.get_ok in
  351. (match q.attachment with
  352. | [a;b;_] ->
  353. a.name |> check string __LOC__ "Support";
  354. b.value |> check string __LOC__ {|<a href="https://seppo.social">Seppo.Social</a>|};
  355. | _ -> check int "" 0 1
  356. );
  357. q.image |> Option.get |> Uri.to_string |> check string __LOC__ "https://example.com/me-banner.jpg";
  358. "" |> check string __LOC__ "";
  359. let q = "data/ap/actor/akkoma.0.json" |> File.in_channel Ezjsonm.from_channel
  360. |> As2_vocab.Activitypub.Decode.person
  361. |> Result.get_ok in
  362. q.name |> Option.get |> check string __LOC__ "Sean Tilley";
  363. let q = "data/ap/actor/gnusocial.0.json" |> File.in_channel Ezjsonm.from_channel
  364. |> As2_vocab.Activitypub.Decode.person
  365. |> Result.get_ok in
  366. q.preferred_username |> Option.get |> check string __LOC__ "diogo";
  367. let q = "data/ap/actor/lemmy.0.json" |> File.in_channel Ezjsonm.from_channel
  368. |> As2_vocab.Activitypub.Decode.person
  369. |> Result.get_ok in
  370. q.preferred_username |> Option.get |> check string __LOC__ "nutomic";
  371. let q = "data/ap/actor/mastodon.0.json" |> File.in_channel Ezjsonm.from_channel
  372. |> As2_vocab.Activitypub.Decode.person
  373. |> Result.get_ok in
  374. q.preferred_username |> Option.get |> check string __LOC__ "ursi";
  375. let q = "data/ap/actor/peertube.0.json" |> File.in_channel Ezjsonm.from_channel
  376. |> As2_vocab.Activitypub.Decode.person
  377. |> Result.get_ok in
  378. q.preferred_username |> Option.get |> check string __LOC__ "edps";
  379. let q = "data/ap/actor/zap.0.json" |> File.in_channel Ezjsonm.from_channel
  380. |> As2_vocab.Activitypub.Decode.person
  381. |> Result.get_ok in
  382. q.preferred_username |> Option.get |> check string __LOC__ "mike";
  383. let q = "data/ap/actor/bridgy.0.json" |> File.in_channel Ezjsonm.from_channel
  384. |> As2_vocab.Activitypub.Decode.person
  385. |> Result.get_ok in
  386. q.preferred_username |> Option.get |> check string __LOC__ "tantek.com";
  387. assert true
  388. )
  389. let tc_encode = "tc_encode", `Quick, (fun () ->
  390. let minify = false in
  391. let base = Uri.of_string "http://example.com/foo/" in
  392. let module E = Decoders_ezjsonm.Encode in
  393. E.encode_string E.obj [("k", `String "v")]
  394. |> check string __LOC__ {|{"k":"v"}|};
  395. Ezjsonm.value_to_string (E.obj [("k", `String "v")])
  396. |> check string __LOC__ {|{"k":"v"}|};
  397. let e = {Rfc4287.Entry.empty with
  398. id = Uri.make ~path:"a/b/" ?fragment:(Some "c") ();
  399. lang = Rfc4287.Rfc4646 "de";
  400. title = "uhu";
  401. published = (Rfc3339.T "2023-03-07T01:23:45Z") ;
  402. updated = (Rfc3339.T "2023-03-07T01:23:46Z");
  403. categories = [
  404. ((Label (Single "lbl")),(Term (Single "term")),Uri.make ~path:"t/" ());
  405. ];
  406. content = "Das war aber einfach";
  407. } in
  408. let n = e |> Ap.Note.of_rfc4287 in
  409. let j = n |> As2_vocab.Encode.note ~base in
  410. j |> Ezjsonm.value_to_string ~minify
  411. |> Assrt.equals_string __LOC__
  412. {|{
  413. "type": "Note",
  414. "id": "http://example.com/foo/a/b/#c",
  415. "attributedTo": "http://example.com/foo/activitypub/actor.jsa",
  416. "to": "https://www.w3.org/ns/activitystreams#Public",
  417. "cc": "http://example.com/foo/activitypub/subscribers/index.jsa",
  418. "mediaType": "text/plain; charset=utf8",
  419. "contentMap": {
  420. "de": "Das war aber einfach"
  421. },
  422. "sensitive": false,
  423. "summaryMap": {
  424. "de": "uhu"
  425. },
  426. "published": "2023-03-07T01:23:45Z",
  427. "tags": {
  428. "type": "Hashtag",
  429. "href": "http://example.com/foo/t/term/",
  430. "name": "#lbl"
  431. }
  432. }|};
  433. let co : 'a As2_vocab.Types.collection_page = {
  434. id = Uri.of_string "http://example.com/foo/";
  435. current = None;
  436. first = None;
  437. is_ordered = true;
  438. items = [Ap.Note.Create.make n];
  439. last = None;
  440. next = None;
  441. part_of = None;
  442. prev = None;
  443. total_items= None;
  444. } in
  445. let j = As2_vocab.Encode.collection_page ~base
  446. (As2_vocab.Encode.create ~base
  447. (As2_vocab.Encode.note ~base))
  448. co in
  449. j |> Ezjsonm.value_to_string ~minify
  450. |> Assrt.equals_string __LOC__ {|{
  451. "@context": [
  452. "https://www.w3.org/ns/activitystreams",
  453. "https://w3id.org/security/v1",
  454. {
  455. "schema": "http://schema.org#",
  456. "PropertyValue": "schema:PropertyValue",
  457. "value": "schema:value",
  458. "@language": "und"
  459. }
  460. ],
  461. "type": "OrderedCollectionPage",
  462. "id": "http://example.com/foo/",
  463. "orderedItems": [
  464. {
  465. "type": "Create",
  466. "id": "http://example.com/foo/a/b/#c/Create",
  467. "actor": "http://example.com/foo/activitypub/actor.jsa",
  468. "published": "2023-03-07T01:23:45Z",
  469. "to": "https://www.w3.org/ns/activitystreams#Public",
  470. "cc": "http://example.com/foo/activitypub/subscribers/index.jsa",
  471. "directMessage": false,
  472. "object": {
  473. "type": "Note",
  474. "id": "http://example.com/foo/a/b/#c",
  475. "attributedTo": "http://example.com/foo/activitypub/actor.jsa",
  476. "to": "https://www.w3.org/ns/activitystreams#Public",
  477. "cc": "http://example.com/foo/activitypub/subscribers/index.jsa",
  478. "mediaType": "text/plain; charset=utf8",
  479. "contentMap": {
  480. "de": "Das war aber einfach"
  481. },
  482. "sensitive": false,
  483. "summaryMap": {
  484. "de": "uhu"
  485. },
  486. "published": "2023-03-07T01:23:45Z",
  487. "tags": {
  488. "type": "Hashtag",
  489. "href": "http://example.com/foo/t/term/",
  490. "name": "#lbl"
  491. }
  492. }
  493. }
  494. ]
  495. }|};
  496. assert true
  497. )
  498. (* https://www.w3.org/TR/activitystreams-core/#ex17-jsonld *)
  499. let tc_ex15_note = "tc_ex15_note", `Quick, (fun () ->
  500. let j = "data/ap/note/as2_core.ex15.json" |> File.in_channel Ezjsonm.value_from_channel in
  501. (match j with
  502. | `O [
  503. "@context", _ ;
  504. "summary", _ ;
  505. "type", `String "Create" ;
  506. "actor", _ ;
  507. "object", `O [
  508. "type", `String "Note" ;
  509. _]
  510. ] -> assert true
  511. | _ -> assert false);
  512. let p = j
  513. |> As2_vocab.Decode.(create note)
  514. |> Result.is_error in
  515. assert p
  516. )
  517. (*
  518. _p.obj.summary |> Option.get |> check string "" "";
  519. *)
  520. (* https://github.com/mattjbray/ocaml-decoders *)
  521. type role = Admin | User
  522. type user =
  523. { lang : string
  524. ; txt : role list
  525. }
  526. let tc_example= "tc_exampl", `Quick, (fun () ->
  527. Logr.info (fun m -> m "%s.%s" "As2_vocab" "example");
  528. let module My_encoders(E : Decoders.Encode.S) = struct
  529. open E
  530. let user : role encoder =
  531. function
  532. | Admin -> string "ADMIN"
  533. | User -> string "USER"
  534. let user : user encoder =
  535. fun u ->
  536. obj
  537. [ ("name", string u.lang)
  538. ; ("roles", list user u.txt)
  539. ]
  540. end in
  541. let module E = Decoders_ezjsonm.Encode in
  542. let module My_ezjson_encoders = My_encoders(Decoders_ezjsonm.Encode) in
  543. let open My_ezjson_encoders in
  544. let users =
  545. [ {lang = "Alice"; txt = [Admin; User]}
  546. ; {lang = "Bob"; txt = [User]}
  547. ] in
  548. E.encode_string E.obj [("users", E.list user users)]
  549. |> check string __LOC__ {|{"users":[{"name":"Alice","roles":["ADMIN","USER"]},{"name":"Bob","roles":["USER"]}]}|}
  550. )
  551. type _i18n =
  552. { lang : string
  553. ; txt : string
  554. }
  555. let tc_encode_content_map = "tc_encode_content_map", `Quick, (fun () ->
  556. Logr.info (fun m -> m "%s.%s" "As2_vocab" "encode_content_map");
  557. let l = [("a","A");("b","B")] in
  558. let module E = Decoders_ezjsonm.Encode in
  559. let j = l |> List.map (fun (k,v) -> (k,E.string v)) in
  560. E.encode_string E.obj j
  561. |> check string __LOC__ {|{"a":"A","b":"B"}|}
  562. )
  563. let tc_decode_content_map = "tc_decode_content_map", `Quick, (fun () ->
  564. Logr.info (fun m -> m "%s.%s" "As2_vocab" "decode_content_map");
  565. let s = Ezjsonm.value_from_string {|{"a":"A","b":"B"}|} in
  566. let module D = Decoders_ezjsonm.Decode in
  567. let l = D.key_value_pairs D.string s
  568. |> Result.get_ok in
  569. (match l with
  570. | [("a","A");("b","B")] -> ()
  571. | _ -> "" |> check string __LOC__ {|{"a":"A","b":"B"}|});
  572. let s = Ezjsonm.value_from_string {|{"contentMap":{"a":"A","b":"B","b":"C"}}|} in
  573. let l = D.field "contentMap" (D.key_value_pairs D.string) s
  574. |> Result.get_ok in
  575. match l with
  576. | [("a","A");("b","B");("b","C")] -> ()
  577. | _ -> "" |> check string __LOC__ {|{"a":"A","b":"B"}|}
  578. )
  579. let tc_decode_natur = "tc_decode_natur", `Quick, (fun () ->
  580. (* https://codeberg.org/seppo/seppo/issues/5 *)
  581. Logr.info (fun m -> m "%s.%s" "As2_vocab" "decode_natur");
  582. let j = "data/ap/actor/natur.0.json" |> File.in_channel Ezjsonm.from_channel in
  583. let e = j |> As2_vocab.Decode.person |> Result.get_error in
  584. let s = e |> Decoders_ezjsonm.Decode.string_of_error in
  585. s |> check string __LOC__ {|Expected an object with an attribute "publicKey", but got
  586. {"id":"https://dev.rdf-pub.org/d613b246-8984-4654-903d-8d44143aca40","type":"Person","inboxSparql":"https://dev.rdf-pub.org/d613b246-8984-4654-903d-8d44143aca40/inbox/sparql","rdfpub:oauth2Issuer":"https://login.m4h.network/auth/realms/LOA","rdfpub:oauth2IssuerPreferredUserName":"max@login.m4h.network","rdfpub:oauth2IssuerUserId":"1813bdc1-152c-4c27-92a6-6cdfe401ef3d@login.m4h.network","outboxSparql":"https://dev.rdf-pub.org/d613b246-8984-4654-903d-8d44143aca40/outbox/sparql","identifier":"52ff7eb2-0b7c-4388-9894-b40a27714c1b","version":{"type":"xsd:integer","@value":"1"},"owl:sameAs":{"id":"https://dev.rdf-pub.org/05a75688-c517-4ae1-842c-5da3d8460627"},"inbox":"https://dev.rdf-pub.org/d613b246-8984-4654-903d-8d44143aca40/inbox","endpoints":{"oauthAuthorizationEndpoint":"https://dev.rdf-pub.org/oauth/oauthAuthorizationEndpoint","oauthTokenEndpoint":"https://dev.rdf-pub.org/oauth/oauthTokenEndpoint"},"name":"max","outbox":"https://dev.rdf-pub.org/d613b246-8984-4654-903d-8d44143aca40/outbox","published":"2024-01-14T15:59:42.102+01:00","@context":["https://schema.org/docs/jsonldcontext.json","https://rdf-pub.org/schema/rdf-pub-context.json","https://www.w3.org/ns/activitystreams"]}|}
  587. )
  588. let tc_decode_sharkey= "tc_decode_sharke", `Quick, (fun () ->
  589. (* https://joinsharkey.org *)
  590. Logr.info (fun m -> m "%s.%s" "As2_vocab" "decode_sharkey");
  591. let j = "data/ap/actor/sharkey.0.json" |> File.in_channel Ezjsonm.from_channel in
  592. let p = j |> As2_vocab.Decode.person |> Result.get_ok in
  593. p.name
  594. |> Option.value ~default:"-"
  595. |> check string __LOC__ {|-|};
  596. ("data/ap/actor/sharkey.1.json" |> File.in_channel Ezjsonm.from_channel
  597. |> As2_vocab.Decode.person
  598. |> Result.get_ok)
  599. .name
  600. |> Option.value ~default:"-"
  601. |> check string __LOC__ {|wakest the shark possum|}
  602. )
  603. let () =
  604. run
  605. "seppo_suite" [
  606. __FILE__ , [
  607. set_up;
  608. tc_actor_3rd;
  609. tc_actor_decode;
  610. tc_actor_encode;
  611. tc_actor_encode_issue35;
  612. tc_profile_sunshine;
  613. tc_note_decode;
  614. tc_create_article_decode;
  615. tc_err;
  616. tc_person;
  617. tc_reject_decode;
  618. tc_webfinger_sunshine;
  619. tc_encode;
  620. tc_ex15_note;
  621. tc_example;
  622. tc_encode_content_map;
  623. tc_decode_content_map;
  624. tc_decode_natur;
  625. tc_decode_sharkey;
  626. ]
  627. ];
  628. assert true