
open Alcotest
open Seppo_lib
module A = Assrt

let set_up = "set_up", `Quick, (fun () ->
    Unix.chdir "../../../test/"
  )

let tc_err = "tc_err", `Quick, (fun () ->
    match {|[]|}
          |> Ezjsonm.from_string
          |> As2_vocab.Activitypub.Decode.obj with
    | Error (e : Decoders_ezjsonm.Decode.error) ->
      e |> Format.asprintf "%a" Decoders_ezjsonm.Decode.pp_error
      |> check string __LOC__ {|I tried the following decoders but they all failed:
  "core_obj" decoder: Expected a string, but got [] 
  "core_obj event" decoder:
    Expected an object with an attribute "type", but got []  |};
      e |> Decoders_ezjsonm.Decode.string_of_error
      |> check string __LOC__ {|I tried the following decoders but they all failed:
  "core_obj" decoder: Expected a string, but got [] 
  "core_obj event" decoder:
    Expected an object with an attribute "type", but got []  |};
    | _ -> failwith __LOC__

   (* diaspora profile json e.g.

      https://pod.diaspora.software/u/hq
      https://pod.diaspora.software/people/7bca7c80311b01332d046c626dd55703
   *)
  )

let tc_person = "tc_person", `Quick, (fun () ->
    let empty : As2_vocab.Types.person = {
      id                          = Uri.empty;
      inbox                       = Uri.empty;
      outbox                      = Uri.empty;
      followers                   = None;
      following                   = None;
      attachment                  = [];
      discoverable                = false;
      generator                   = None;
      icon                        = [];
      image                       = None;
      manually_approves_followers = false;
      name                        = None;
      name_map                    = [];
      preferred_username          = None;
      preferred_username_map      = [];
      public_key                  = {
        id                        = Uri.empty;
        owner                     = None;
        pem                       = "";
        signatureAlgorithm        = None;
      };
      published                   = Some Ptime.epoch;
      summary                     = None;
      summary_map                 = [];
      url                         = [];
    } in
    let base = (Uri.of_string "https://example.com/su/") in
    let lang = As2_vocab.Constants.ActivityStreams.und in
    {empty with id=Uri.make ~path:"id/" ()}
    |> As2_vocab.Encode.person ~lang ~base
    |> Ezjsonm.value_to_string ~minify:false
    |> check string __LOC__ {|{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://w3id.org/security/v1",
    {
      "schema": "http://schema.org#",
      "PropertyValue": "schema:PropertyValue",
      "value": "schema:value",
      "@language": "und"
    }
  ],
  "type": "Person",
  "id": "https://example.com/su/id/",
  "inbox": "https://example.com/su/",
  "outbox": "https://example.com/su/",
  "publicKey": {
    "@context": [
      {
        "@language": null
      }
    ],
    "id": "https://example.com/su/",
    "publicKeyPem": ""
  },
  "published": "1970-01-01T00:00:00Z",
  "manuallyApprovesFollowers": false,
  "discoverable": false
}|};
    let p = {|{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://w3id.org/security/v1",
    {
      "schema": "http://schema.org#",
      "PropertyValue": "schema:PropertyValue",
      "value": "schema:value",
      "@language": "und"
    }
  ],
  "type": "Person",
  "id": "https://example.com/su/id/",
  "inbox": "https://example.com/su/",
  "outbox": "https://example.com/su/",
  "publicKey": {
    "id": "https://example.com/su/",
    "owner": "https://example.com/su/",
    "publicKeyPem": ""
  },
  "published": "1970-01-01T00:00:00Z",
  "manuallyApprovesFollowers": false,
  "discoverable": false,
  "attachment": []
}|}
            |> Ezjsonm.value_from_string
            |> As2_vocab.Decode.person
            |> Result.get_ok in
    p.id
    |> Uri.to_string
    |> check string __LOC__ "https://example.com/su/id/"
  )

let tc_actor_3rd = "tc_actor_3rd", `Quick, (fun () ->
    Logr.info (fun m -> m "%s.%s" "As2_vocab" "actor_3rd");
    let ok loc fn na =
      let j = fn |> File.in_channel Ezjsonm.from_channel in
      let p = j |> As2_vocab.Decode.person |> Result.get_ok in
      p.name |> Option.get |> check string loc na
    and oki loc fn id =
      let j = fn |> File.in_channel Ezjsonm.from_channel in
      let p = j |> As2_vocab.Decode.person |> Result.get_ok in
      p.id |> Uri.to_string |> check string loc (id)
    and err loc fn e =
      let j = fn |> File.in_channel Ezjsonm.from_channel in
      j |> As2_vocab.Decode.person |> Result.get_error
      |> Decoders_ezjsonm.Decode.string_of_error
      |> check string loc e
    and fal _loc fn =
      let j = fn |> File.in_channel Ezjsonm.from_channel in
      assert (j |> As2_vocab.Decode.person |> Result.is_error)
    in
    (* ok __LOC__ "data/ap/actor/lemmy.0.json" ""; *)
    ok __LOC__ "data/ap/actor/akkoma.0.json" "Sean Tilley";
    ok __LOC__ "data/ap/actor/akkoma.1.json" "Kinetix";
    ok __LOC__ "data/ap/actor/bonfire.0.json" "stpaultim";
    ok __LOC__ "data/ap/actor/bridgy.0.json" "Tantek Çelik";
    ok __LOC__ "data/ap/actor/friendica.0.json" "Michael Vogel";
    ok __LOC__ "data/ap/actor/gnusocial.0.json" "Diogo Peralta Cordeiro";
    ok __LOC__ "data/ap/actor/gnusocial.1.json" "admin de gnusocial.net";
    ok __LOC__ "data/ap/actor/gnusocial.2.json" "diogo";
    ok __LOC__ "data/ap/actor/gotosocial.0.json" "Gerben";
    ok __LOC__ "data/ap/actor/gotosocial.1.json" "Gerben";
    oki __LOC__ "data/ap/actor/gotosocial.1b.json" "https://social.nlnet.nl/users/gerben";
    ok __LOC__ "data/ap/actor/honk.0.json" "boyter";
    ok __LOC__ "data/ap/actor/mastodon.0.json" "Yet Another #Seppo! 🌻";
    ok __LOC__ "data/ap/actor/mastodon.1.json" "#Seppo";
    ok __LOC__ "data/ap/actor/mastodon.2.json" "Marcus Rohrmoser 🌍";
    ok __LOC__ "data/ap/actor/mini.0.json" "Yet Another #Seppo! 🌻";
    ok __LOC__ "data/ap/actor/misskey.0.json" "しゅいろ";
    ok __LOC__ "data/ap/actor/peertube.0.json" "edps";
    ok __LOC__ "data/ap/actor/peertube.1.json" {|Q3 Marcus|};
    ok __LOC__ "data/ap/actor/peertube.2.json" {|edps|};
    ok __LOC__ "data/ap/actor/peertube.3.json" {|Framasoft|};
    ok __LOC__ "data/ap/actor/pixelfed.0.json" {|#Seppo|};
    ok __LOC__ "data/ap/actor/pleroma.0.json" "@fediverse@mro.name";
    ok __LOC__ "data/ap/actor/smithereen.0.json" {|Григорий Клюшников|};
    ok __LOC__ "data/ap/actor/snac.0.json" {|The Real Grunfink|};
    ok __LOC__ "data/ap/actor/threads.0.json" "Ben Savage";
    ok __LOC__ "data/ap/actor/tootik.0.json" {|EOIN GAIRLEOG|};
    ok __LOC__ "data/ap/actor/zap.0.json" "mike";
    ok __LOC__ "data/ap/profile/@actapopuli@fediverse.blog.json" "actapopuli";
    ok __LOC__ "data/ap/profile/@administrator@gnusocial.net.json" {|admin de gnusocial.net|};
    ok __LOC__ "data/ap/profile/@dansup@pixelfed.social.json" {|dansup|};
    ok __LOC__ "data/ap/profile/@gargron@mastodon.social.json" {|Eugen Rochko|};
    ok __LOC__ "data/ap/profile/@kainoa@calckey.social.json" {|Kainoa |};
    ok __LOC__ "data/ap/profile/@karolat@stereophonic.space.json" {|karolat|};
    ok __LOC__ "data/ap/profile/@manton@manton.org.json" {|Manton Reece|};
    ok __LOC__ "data/ap/profile/@matt@write.as.json" {|Matt|};
    ok __LOC__ "data/ap/profile/@mike@macgirvin.com.json" {|Mike Macgirvin|};
    ok __LOC__ "data/ap/profile/@peertube@framapiaf.org.json" {|PeerTube|};
    ok __LOC__ "data/ap/profile/@syuilo@misskey.io.json" {|:peroro_sama:しゅいろ:peroro_sama:|};
    ok __LOC__ "data/ap/profile/@tobias@friendi.ca.json" {|Tobias|};
    (*
    err __LOC__ "data/ap/actor/gotosocial.1b.json" {|Expected an object with an attribute "inbox", but got
{"@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"}|};
*)
    err __LOC__ "data/ap/actor/friendica.1.json" {|I tried the following decoders but they all failed:
  "type" decoder:
    in field "type":
      expected Person (received Organization), but got "Organization" 
  "type" decoder:
    in field "type":
      expected Service (received Organization), but got "Organization"  |};
    err __LOC__ "data/ap/profile/@lemmy_support@lemmy.ml.json" {|I tried the following decoders but they all failed:
  "type" decoder:
    in field "type": expected Person (received Group), but got "Group" 
  "type" decoder:
    in field "type": expected Service (received Group), but got "Group"  |};
    err __LOC__ "data/ap/actor/mobilizon.0.json" {|I tried the following decoders but they all failed:
  "type" decoder:
    in field "type": expected Person (received Group), but got "Group" 
  "type" decoder:
    in field "type": expected Service (received Group), but got "Group"  |};
    fal __LOC__ "data/ap/actor/diaspora.0.json";
    ()
(*
  ok __LOC__ "data/ap/actor/natur.0.json" "しゅいろ";
  ok __LOC__ "data/ap/actor/natur.1.json" "しゅいろ";
  ok __LOC__ "data/ap/actor/sharkey.0.json" {||};
  ok __LOC__ "data/ap/actor/sharkey.1.json" {||};

 ok __LOC__ "data/ap/profile/@framasoft@mobilizon.fr.json" {||};
 ok __LOC__ "data/ap/profile/@Greensky@open.audio.json" {||};
*)
  )

let tc_actor_decode = "tc_actor_decode", `Quick, (fun () ->
    let fn = "data/ap/actor/peertube.3.json" in
    let j = fn |> File.in_channel Ezjsonm.from_channel in
    let p = j |> As2_vocab.Decode.person |> Result.get_ok in
    p.name |> Option.get |> check string __LOC__ "Framasoft";
    p.icon |> List.length |> check int __LOC__ 2;
    ()
  )

let tc_actor_encode = "tc_actor_encode", `Quick, (fun () ->
    let base    = Uri.empty
    and lang    = None
    and minify  = false
    in
    let s = "data/ap/actor/peertube.3.json"
            |> File.in_channel Ezjsonm.from_channel
            |> As2_vocab.Decode.person |> Result.get_ok
            |> As2_vocab.Encode.person ~base ~lang
            |> Ezjsonm.value_to_string ~minify in
    s |>
    check string
    (*
    check string
    *)
      __LOC__ {|{
  "type": "Person",
  "id": "https://framatube.org/accounts/framasoft",
  "inbox": "https://framatube.org/accounts/framasoft/inbox",
  "outbox": "https://framatube.org/accounts/framasoft/outbox",
  "followers": "https://framatube.org/accounts/framasoft/followers",
  "following": "https://framatube.org/accounts/framasoft/following",
  "name": "Framasoft",
  "url": "https://framatube.org/accounts/framasoft",
  "preferredUsername": "framasoft",
  "publicKey": {
    "@context": [
      {
        "@language": null
      }
    ],
    "id": "https://framatube.org/accounts/framasoft#main-key",
    "owner": "https://framatube.org/accounts/framasoft",
    "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-----"
  },
  "published": "2018-03-01T15:16:17Z",
  "manuallyApprovesFollowers": false,
  "discoverable": false,
  "icon": [
    {
      "type": "Image",
      "url": "https://framatube.org/lazy-static/avatars/1dbda4f0-1f7f-40f2-b962-85fd0c144661.png"
    },
    {
      "type": "Image",
      "url": "https://framatube.org/lazy-static/avatars/f73876f5-1d45-4f8a-942a-d3d5d5ac5dc1.png"
    }
  ]
}|}
  )

let tc_actor_encode_issue35 = "tc_actor_encode", `Quick, (fun () ->
    let base = Uri.empty
    and url = [ "https://example.com" |> Uri.of_string ]
    in
    let (<:) = function
      | (_, None) -> fun _ -> []
      | (field, Some vl) -> fun ty -> [field, ty vl] in
    let (@?.) field vl = (field, match vl with | [] -> None | l -> Some l) in
    let list (a : 'a Decoders_ezjsonm.Encode.encoder) (b : 'a list) : Ezjsonm.value = match b with
      | []  -> Decoders_ezjsonm.Encode.null (* none => omit *)
      | [b] -> Decoders_ezjsonm.Encode.encode_value a b (* single => value *)
      | b   -> Decoders_ezjsonm.Encode.list a b in
    let uri ~base u = u |> Uri.resolve "https" base |> Uri.to_string |> Decoders_ezjsonm.Encode.string in
    (match
       "url"                       @?. url      <: list (uri ~base);
     with
     | [("url",j)] -> j
                      |> Ezjsonm.value_to_string
                      |> check string __LOC__ {|"https://example.com"|}
     | _ -> fail __LOC__
    ))

let tc_note_decode = "tc_note_dcode", `Quick, (fun () ->
    let j = "data/ap/note/mastodon.json" |> File.in_channel Ezjsonm.from_channel in
    let n = j |> As2_vocab.Decode.note |> Result.get_ok in
    n.id |> Uri.to_string |> check string __LOC__ "https://digitalcourage.social/users/mro/statuses/111403080326863922";
    (match n.in_reply_to with
     | [u] -> u |> Uri.to_string |> check string __LOC__ "https://chaos.social/users/qyliss/statuses/111403054651938519"
     | _ -> failwith "none");
    let n = "data/ap/inbox/create/note/akkoma.json" |> File.in_channel Ezjsonm.from_channel
            |> As2_vocab.Decode.(create note) |> Result.get_ok in
    let _,co = n.obj.content_map |> List.hd in
    co
    |> Assrt.equals_string __LOC__
      {|<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>|}
  )

let tc_create_article_decode = "tc_create_article_decode", `Quick, (fun () ->
    "data/ap/inbox/create/article/friendica.json"
    |> File.in_channel Ezjsonm.from_channel
    |> As2_vocab.Decode.(create note) |> Result.get_error
    |> Decoders_ezjsonm.Decode.string_of_error
    |> check string __LOC__ "in field \"object\":\n  in field \"type\": expected Note (received Article), but got \"Article\""
  )

let tc_reject_decode = "tc_reject_decode", `Quick, (fun () ->
    Logr.info (fun m -> m "%s.%s" "As2_vocab" "reject_decode");
    let j = "data/ap/inbox/reject/follow/2024-08-17-124924-steve.json" |> File.in_channel Ezjsonm.from_channel in
    let n = j |> As2_vocab.Decode.(reject follow) |> Result.get_ok in
    n.id
    |> Uri.to_string
    |> check string __LOC__ {|https://social.technoetic.com/users/steve#rejects/follows/|};
    n.obj.id
    |> Uri.to_string
    |> check string __LOC__ {|https://social.technoetic.com/users/steve#subscribe|}
  )

let tc_webfinger_sunshine = "tc_webfinger_sunshine", `Quick, (fun () ->
    let q = "data/webfinger/mini.json" |> File.in_channel Ezjsonm.from_channel
            |> As2_vocab.Activitypub.Decode.Webfinger.query_result
            |> Result.get_ok in
    q.subject |> check string __LOC__ "acct:ursi@example.com";
    q.links |> List.length |> check int __LOC__ 3;
    let q = "data/webfinger/zap.json" |> File.in_channel Ezjsonm.from_channel
            |> As2_vocab.Activitypub.Decode.Webfinger.query_result
            |> Result.get_ok in
    q.subject |> check string __LOC__ "acct:mike@macgirvin.com";
    q.links |> List.length |> check int __LOC__ 3;
    let q = "data/webfinger/atom.json" |> File.in_channel Ezjsonm.from_channel
            |> As2_vocab.Activitypub.Decode.Webfinger.query_result
            |> Result.get_ok in
    q.subject |> check string __LOC__ "acct:ursi@example.com";
    q.links |> List.length |> check int __LOC__ 3
  )

let tc_profile_sunshine = "tc_profile_sunshine", `Quick, (fun () ->
    let q = "data/ap/actor/mini.0.json" |> File.in_channel Ezjsonm.from_channel
            |> As2_vocab.Activitypub.Decode.person
            |> Result.get_ok in
    q.name |> Option.get |> check string __LOC__ "Yet Another #Seppo! 🌻";
    q.preferred_username |> Option.get |> check string __LOC__ "ursi";
    q.attachment |> List.length |> check int "" 0;
    let q = "data/ap/actor/mastodon.0.json" |> File.in_channel Ezjsonm.from_channel
            |> As2_vocab.Activitypub.Decode.person
            |> Result.get_ok in
    (match q.attachment with
     | [a;b;_] ->
       a.name |> check string __LOC__ "Support";
       b.value |> check string __LOC__ {|<a href="https://seppo.social">Seppo.Social</a>|};
     | _ -> check int "" 0 1
    );
    q.image |> Option.get |> Uri.to_string |> check string __LOC__ "https://example.com/me-banner.jpg";
    "" |> check string __LOC__ "";
    let q = "data/ap/actor/akkoma.0.json" |> File.in_channel Ezjsonm.from_channel
            |> As2_vocab.Activitypub.Decode.person
            |> Result.get_ok in
    q.name |> Option.get |> check string __LOC__ "Sean Tilley";
    let q = "data/ap/actor/gnusocial.0.json" |> File.in_channel Ezjsonm.from_channel
            |> As2_vocab.Activitypub.Decode.person
            |> Result.get_ok in
    q.preferred_username |> Option.get |> check string __LOC__ "diogo";
    let q = "data/ap/actor/lemmy.0.json" |> File.in_channel Ezjsonm.from_channel
            |> As2_vocab.Activitypub.Decode.person
            |> Result.get_ok in
    q.preferred_username |> Option.get |> check string __LOC__ "nutomic";
    let q = "data/ap/actor/mastodon.0.json" |> File.in_channel Ezjsonm.from_channel
            |> As2_vocab.Activitypub.Decode.person
            |> Result.get_ok in
    q.preferred_username |> Option.get |> check string __LOC__ "ursi";
    let q = "data/ap/actor/peertube.0.json" |> File.in_channel Ezjsonm.from_channel
            |> As2_vocab.Activitypub.Decode.person
            |> Result.get_ok in
    q.preferred_username |> Option.get |> check string __LOC__ "edps";
    let q = "data/ap/actor/zap.0.json" |> File.in_channel Ezjsonm.from_channel
            |> As2_vocab.Activitypub.Decode.person
            |> Result.get_ok in
    q.preferred_username |> Option.get |> check string __LOC__ "mike";
    let q = "data/ap/actor/bridgy.0.json" |> File.in_channel Ezjsonm.from_channel
            |> As2_vocab.Activitypub.Decode.person
            |> Result.get_ok in
    q.preferred_username |> Option.get |> check string __LOC__ "tantek.com";
    assert true
  )

let tc_encode = "tc_encode", `Quick, (fun () ->
    let minify = false in
    let base = Uri.of_string "http://example.com/foo/" in
    let module E = Decoders_ezjsonm.Encode in
    E.encode_string E.obj [("k", `String "v")]
    |> check string __LOC__ {|{"k":"v"}|};
    Ezjsonm.value_to_string (E.obj [("k", `String "v")])
    |> check string __LOC__ {|{"k":"v"}|};

    let e = {Rfc4287.Entry.empty with
             id         = Uri.make ~path:"a/b/" ?fragment:(Some "c") ();
             lang       = Rfc4287.Rfc4646 "de";
             title      = "uhu";
             published  = (Rfc3339.T "2023-03-07T01:23:45Z") ;
             updated    = (Rfc3339.T "2023-03-07T01:23:46Z");
             categories = [
               ((Label (Single "lbl")),(Term (Single "term")),Uri.make ~path:"t/" ());
             ];
             content    = "Das war aber einfach";
            } in
    let n = e |> Ap.Note.of_rfc4287 in
    let j = n |> As2_vocab.Encode.note ~base in
    j |> Ezjsonm.value_to_string ~minify
    |> Assrt.equals_string __LOC__
      {|{
  "type": "Note",
  "id": "http://example.com/foo/a/b/#c",
  "attributedTo": "http://example.com/foo/activitypub/actor.jsa",
  "to": "https://www.w3.org/ns/activitystreams#Public",
  "cc": "http://example.com/foo/activitypub/subscribers/index.jsa",
  "mediaType": "text/plain; charset=utf8",
  "contentMap": {
    "de": "Das war aber einfach"
  },
  "sensitive": false,
  "summaryMap": {
    "de": "uhu"
  },
  "published": "2023-03-07T01:23:45Z",
  "tags": {
    "type": "Hashtag",
    "href": "http://example.com/foo/t/term/",
    "name": "#lbl"
  }
}|};

    let co : 'a As2_vocab.Types.collection_page = {
      id = Uri.of_string "http://example.com/foo/";
      current    = None;
      first      = None;
      is_ordered = true;
      items      = [Ap.Note.Create.make n];
      last       = None;
      next       = None;
      part_of    = None;
      prev       = None;
      total_items= None;
    } in
    let j = As2_vocab.Encode.collection_page ~base
        (As2_vocab.Encode.create ~base
           (As2_vocab.Encode.note ~base))
        co in
    j |> Ezjsonm.value_to_string ~minify
    |> Assrt.equals_string __LOC__ {|{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://w3id.org/security/v1",
    {
      "schema": "http://schema.org#",
      "PropertyValue": "schema:PropertyValue",
      "value": "schema:value",
      "@language": "und"
    }
  ],
  "type": "OrderedCollectionPage",
  "id": "http://example.com/foo/",
  "orderedItems": [
    {
      "type": "Create",
      "id": "http://example.com/foo/a/b/#c/Create",
      "actor": "http://example.com/foo/activitypub/actor.jsa",
      "published": "2023-03-07T01:23:45Z",
      "to": "https://www.w3.org/ns/activitystreams#Public",
      "cc": "http://example.com/foo/activitypub/subscribers/index.jsa",
      "directMessage": false,
      "object": {
        "type": "Note",
        "id": "http://example.com/foo/a/b/#c",
        "attributedTo": "http://example.com/foo/activitypub/actor.jsa",
        "to": "https://www.w3.org/ns/activitystreams#Public",
        "cc": "http://example.com/foo/activitypub/subscribers/index.jsa",
        "mediaType": "text/plain; charset=utf8",
        "contentMap": {
          "de": "Das war aber einfach"
        },
        "sensitive": false,
        "summaryMap": {
          "de": "uhu"
        },
        "published": "2023-03-07T01:23:45Z",
        "tags": {
          "type": "Hashtag",
          "href": "http://example.com/foo/t/term/",
          "name": "#lbl"
        }
      }
    }
  ]
}|};
    assert true
  )

(* https://www.w3.org/TR/activitystreams-core/#ex17-jsonld *)
let tc_ex15_note = "tc_ex15_note", `Quick, (fun () ->
    let j = "data/ap/note/as2_core.ex15.json" |> File.in_channel Ezjsonm.value_from_channel in
    (match j with
     | `O [
         "@context", _ ;
         "summary", _ ;
         "type", `String "Create" ;
         "actor",  _ ;
         "object", `O [
           "type", `String "Note" ;
           _]
       ] -> assert true
     | _ -> assert false);
    let p = j
            |> As2_vocab.Decode.(create note)
            |> Result.is_error in
    assert p
  )
  (*
  _p.obj.summary |> Option.get |> check string "" "";
*)

(* https://github.com/mattjbray/ocaml-decoders *)
type role = Admin | User

type user =
  { lang : string
  ; txt  : role list
  }

let tc_example= "tc_exampl", `Quick, (fun () ->
    Logr.info (fun m -> m "%s.%s" "As2_vocab" "example");
    let module My_encoders(E : Decoders.Encode.S) = struct
      open E

      let user : role encoder =
        function
        | Admin -> string "ADMIN"
        | User -> string "USER"

      let user : user encoder =
        fun u ->
        obj
          [ ("name", string u.lang)
          ; ("roles", list user u.txt)
          ]
    end in
    let module E = Decoders_ezjsonm.Encode in
    let module My_ezjson_encoders = My_encoders(Decoders_ezjsonm.Encode) in
    let open My_ezjson_encoders in
    let users =
      [ {lang = "Alice"; txt = [Admin; User]}
      ; {lang = "Bob"; txt = [User]}
      ] in
    E.encode_string E.obj [("users", E.list user users)]
    |> check string __LOC__ {|{"users":[{"name":"Alice","roles":["ADMIN","USER"]},{"name":"Bob","roles":["USER"]}]}|}
  )

type _i18n =
  { lang : string
  ; txt  : string
  }

let tc_encode_content_map = "tc_encode_content_map", `Quick, (fun () ->
    Logr.info (fun m -> m "%s.%s" "As2_vocab" "encode_content_map");
    let l = [("a","A");("b","B")] in
    let module E = Decoders_ezjsonm.Encode in
    let j = l |> List.map (fun (k,v) -> (k,E.string v)) in
    E.encode_string E.obj j
    |> check string __LOC__ {|{"a":"A","b":"B"}|}
  )

let tc_decode_content_map = "tc_decode_content_map", `Quick, (fun () ->
    Logr.info (fun m -> m "%s.%s" "As2_vocab" "decode_content_map");
    let s = Ezjsonm.value_from_string {|{"a":"A","b":"B"}|} in
    let module D = Decoders_ezjsonm.Decode in
    let l = D.key_value_pairs D.string s
            |> Result.get_ok in
    (match l with
     | [("a","A");("b","B")] -> ()
     | _ ->  "" |> check string __LOC__ {|{"a":"A","b":"B"}|});
    let s = Ezjsonm.value_from_string {|{"contentMap":{"a":"A","b":"B","b":"C"}}|} in
    let l = D.field "contentMap" (D.key_value_pairs D.string) s
            |> Result.get_ok in
    match l with
    | [("a","A");("b","B");("b","C")] -> ()
    | _ ->  "" |> check string __LOC__ {|{"a":"A","b":"B"}|}
  )

let tc_decode_natur = "tc_decode_natur", `Quick, (fun () ->
    (* https://codeberg.org/seppo/seppo/issues/5 *)
    Logr.info (fun m -> m "%s.%s" "As2_vocab" "decode_natur");
    let j = "data/ap/actor/natur.0.json" |> File.in_channel Ezjsonm.from_channel in
    let e = j |> As2_vocab.Decode.person |> Result.get_error in
    let s = e |> Decoders_ezjsonm.Decode.string_of_error in
    s |> check string __LOC__ {|Expected an object with an attribute "publicKey", but got
{"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"]}|}
  )

let tc_decode_sharkey= "tc_decode_sharke", `Quick, (fun () ->
    (* https://joinsharkey.org *)
    Logr.info (fun m -> m "%s.%s" "As2_vocab" "decode_sharkey");
    let j = "data/ap/actor/sharkey.0.json" |> File.in_channel Ezjsonm.from_channel in
    let p = j |> As2_vocab.Decode.person |> Result.get_ok in
    p.name
    |> Option.value ~default:"-"
    |> check string __LOC__ {|-|};
    ("data/ap/actor/sharkey.1.json" |> File.in_channel Ezjsonm.from_channel
     |> As2_vocab.Decode.person
     |> Result.get_ok)
    .name
    |> Option.value ~default:"-"
    |> check string __LOC__ {|wakest the shark possum|}
  )

let () =
  run
    "seppo_suite" [
    __FILE__ , [
      set_up;
      tc_actor_3rd;
      tc_actor_decode;
      tc_actor_encode;
      tc_actor_encode_issue35;
      tc_profile_sunshine;
      tc_note_decode;
      tc_create_article_decode;
      tc_err;
      tc_person;
      tc_reject_decode;
      tc_webfinger_sunshine;
      tc_encode;
      tc_ex15_note;
      tc_example;
      tc_encode_content_map;
      tc_decode_content_map;
      tc_decode_natur;
      tc_decode_sharkey;
    ]
  ];
  assert true

