(*
 *    _  _   ____                         _  
 *  _| || |_/ ___|  ___ _ __  _ __   ___ | | 
 * |_  ..  _\___ \ / _ \ '_ \| '_ \ / _ \| | 
 * |_      _|___) |  __/ |_) | |_) | (_) |_| 
 *   |_||_| |____/ \___| .__/| .__/ \___/(_) 
 *                     |_|   |_|             
 *
 * Personal Social Web.
 *
 * t_as2.ml
 *
 * Copyright (C) The #Seppo contributors. All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *)


open Seppo_lib
open Alcotest
module A = Assrt

let set_up () =
  Mirage_crypto_rng_unix.use_default ();
  Unix.chdir "../../../test/"

let tc_follower_state () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_follower_state");
  Ap.Followers.State.(Pending |> to_string) |>
  check string __LOC__ "pending"

let tc_inbox_follow () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_inbox_follow");
  let a =  "data/follow.mastodon.json"
           |> File.in_channel Ezjsonm.from_channel
           |> As2_vocab.Activitypub.Decode.follow
           |> Result.get_ok in
  a.id |> A.equals_uri __LOC__ (Uri.of_string "https://alpaka.social/afe4ac8a-aaeb-41f5-a348-e5d133bdb931");
  a.actor |> A.equals_uri __LOC__ (Uri.of_string "https://alpaka.social/users/traunstein");
  a.object_ |> A.equals_uri __LOC__ (Uri.of_string "https://dev.seppo.social/2023-06-12/activitypub/profile.json");
  match a.state with
  | None -> ()
  | _ -> "-" |> A.equals_string __LOC__ ""

let tc_inbox_unfollow () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_inbox_unfollow");
  let o = "data/undo-follow.mastodon.json"
          |> File.in_channel Ezjsonm.from_channel
          |> As2_vocab.Activitypub.Decode.(undo follow)
          |> Result.get_ok in
  o.id |> Uri.to_string |> A.equals_string __LOC__ "https://alpaka.social/users/traunstein#follows/5219/undo";
  o.actor |> Uri.to_string |> A.equals_string __LOC__ "https://alpaka.social/users/traunstein";
  o.obj.id |> Uri.to_string |> A.equals_string __LOC__ "https://alpaka.social/a5fc3ab5-92f3-4641-a33e-96418e9bec78";
  o.obj.object_ |> Uri.to_string |> A.equals_string __LOC__ "https://dev.seppo.social/2023-06-12/activitypub/profile.json";
  assert (o.actor |> Uri.equal o.obj.actor);
(*
  a.object_ |> A.equals_uri __LOC__ (Uri.of_string "https://dev.seppo.mro.name/2023-06-12/activitypub/profile.json");
  match a.state with
  | None -> ()
  | _ -> "-" |> A.equals_string __LOC__ ""
*)
  assert true

let tc_followers_page_json_raw () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_followers_page_json_raw");
  let fs = [
    "1/" |> Uri.of_string;
    "2/" |> Uri.of_string;
    "3/" |> Uri.of_string;
    "4/" |> Uri.of_string;
    "5/" |> Uri.of_string;
  ] in
  let base' = "https://example.com/u/" |> Uri.of_string in
  let base = "https://example.com/ap/followers/2.jsa" |> Uri.of_string in
  let p : Uri.t As2_vocab.Types.collection_page = {
    id         = base;
    current    = Some base;
    first      = None;
    is_ordered = true;
    items      = fs;
    last       = Some (Http.reso ~base (Uri.of_string "0.jsa"));
    next       = Some (Http.reso ~base (Uri.of_string "1.jsa"));
    part_of    = Some (Http.reso ~base (Uri.of_string "index.jsa"));
    prev       = Some (Http.reso ~base (Uri.of_string "3.jsa"));
    total_items= None;
  } in
  p |> As2_vocab.Encode.(collection_page ~base (uri ~base:base'))
  |> Ezjsonm.value_to_string ~minify:false
  |> A.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": "https://example.com/ap/followers/2.jsa",
  "current": "https://example.com/ap/followers/2.jsa",
  "last": "https://example.com/ap/followers/0.jsa",
  "next": "https://example.com/ap/followers/1.jsa",
  "partOf": "https://example.com/ap/followers/index.jsa",
  "prev": "https://example.com/ap/followers/3.jsa",
  "orderedItems": [
    "https://example.com/u/1/",
    "https://example.com/u/2/",
    "https://example.com/u/3/",
    "https://example.com/u/4/",
    "https://example.com/u/5/"
  ]
}|}

let tc_followers_page_json () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_followers_page_json");
  let foo = "https://example.com/foo/usr/" |> Uri.of_string in
  let base = "https://example.com/" |> Uri.of_string in
  let path = Ap.apub ^ "followers/" in
  let base = Http.reso ~base (Uri.make ~path ()) in
  [ "1/"; "2/"; "3/"; "4/"; "5/" ]
  |> List.rev
  |> List.fold_left (fun init path -> (Uri.make ~path () |> Http.reso ~base:foo) :: init) []
  |> Ap.Followers.Json.to_page_json ~base path ~is_last:false 2
  |> Ezjsonm.value_to_string ~minify:false
  |> A.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": "https://example.com/activitypub/followers/2.jsa",
  "current": "https://example.com/activitypub/followers/2.jsa",
  "last": "https://example.com/activitypub/followers/0.jsa",
  "next": "https://example.com/activitypub/followers/1.jsa",
  "partOf": "https://example.com/activitypub/followers/index.jsa",
  "prev": "https://example.com/activitypub/followers/3.jsa",
  "orderedItems": [
    "https://example.com/foo/usr/1/",
    "https://example.com/foo/usr/2/",
    "https://example.com/foo/usr/3/",
    "https://example.com/foo/usr/4/",
    "https://example.com/foo/usr/5/"
  ]
}|}

let tc_followers_json () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_followers_json");
  let foo = "https://example.com/foo/usr/" |> Uri.of_string in
  let lst = [ "1/"; "2/"; "3/"; "4/"; "5/" ]
            |> List.rev
            |> List.fold_left (fun init path -> (Uri.make ~path () |> Http.reso ~base:foo) :: init) []
  in
  let base = "https://example.com/" |> Uri.of_string in
  let path = Ap.apub ^ "followers/" in
  let base = Http.reso ~base (Uri.make ~path ()) in
  let _ = File.mkdir_p File.pDir path in
  (* let oc = stdout in *)
  "/dev/null" |> File.out_channel_append (fun oc ->
      Ap.Followers.Json.(List.fold_left
                           (fold2pages 3 (flush_page_json ~base ~oc path))
                           (0,0,[],0)
                           lst
                         |> flush_page_json ~base ~is_last:true ~oc path);
      (* check existence of index.jsa *)
      (* check existence of 0.jsa *)
      (* check existence of 1.jsa *)
      "" |> A.equals_string __LOC__ ""
    )

let tc_followers_cdb_json () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_followers_cdb_json");
  "/dev/null" |> File.out_channel_append (fun oc ->
      let base = "https://example.com/" |> Uri.of_string in
      let path = Ap.apub ^ "follower2/" in
      let base = Http.reso ~base (Uri.make ~path ()) in
      let path = "tmp/" ^ path in
      let _ = File.mkdir_p File.pDir path in
      let _ = Ap.Followers.Json.coll_of_cdb ~base ~oc ~pagesize:3 path (Mapcdb.Cdb "data/followers.cdb") in
      "" |> A.equals_string __LOC__ ""
    )

module Note = struct
  let tc_of_rfc4287 () =
    let open Rfc4287 in
    let e = {Entry.empty with
             id         = "" |> Uri.of_string;
             lang       = Rfc4646 "nl";
             title      = "Title";
             published  = Rfc3339.T "2023-10-24T11:12:13+02:00";
             updated    = Rfc3339.T "2023-10-24T11:12:13+02:00";
             links      = [Link.make ("https://Seppo.mro.name/demo" |> Uri.of_string) ];
             content    = "string";
            } in
    e.title |> check string __LOC__ "Title";
    let n = e |> Ap.Note.of_rfc4287 in
    let _,su = n.summary_map |> List.hd in
    su |> check string __LOC__ "Title";
    n.url |> List.hd |> Uri.to_string |> check string __LOC__ "https://seppo.mro.name/demo"

  let tc_create_note_json () =
    Logr.info (fun m -> m "%s.%s" "Ap_test" "test_create_note_json");
    "data/create.note.mastodon2.json" |> File.in_channel (fun ic ->
        let c : As2_vocab.Types.note As2_vocab.Types.create = Ezjsonm.from_channel ic
                                                              |> As2_vocab.Decode.(create note)
                                                              |> Result.get_ok in
        assert (not c.direct_message);
        let n = c.obj in
        n.id |> Uri.to_string |> A.equals_string __LOC__ "https://bewegung.social/users/mro/statuses/111561416759041219";
        n.attributed_to |> Uri.to_string |> A.equals_string __LOC__ "https://bewegung.social/users/mro";
        n.published |> Option.get |> Ptime.to_rfc3339 |> A.equals_string __LOC__ "2023-12-11T10:55:25-00:00";
        n.in_reply_to |> List.length |> A.equals_int __LOC__ 0;
        assert (n.summary_map |> List.length = 0);
        let _,co = n.content_map |> List.hd in
        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>|};
        n.tags |> List.length |> A.equals_int __LOC__ 3;
        (match n.tags with
         | [
           {ty=`Mention;name=n0;href=h0};
           {ty=`Hashtag;name=n1;href=h1};
           {ty=`Hashtag;name=n2;href=h2};
         ] ->
           n0 |> A.equals_string __LOC__ "@demo@seppo.social";
           h0 |> Uri.to_string |> A.equals_string __LOC__ "https://seppo.social/demo/activitypub/profile.jlda";
           n1 |> A.equals_string __LOC__ "#social";
           h1 |> Uri.to_string |> A.equals_string __LOC__ "https://bewegung.social/tags/social";
           n2 |> A.equals_string __LOC__ "#web";
           h2 |> Uri.to_string |> A.equals_string __LOC__ "https://bewegung.social/tags/web";
           ()
         | _ -> failwith "aua"
         (**) );
        assert true
      )

  let tc_diluviate () =
    let n =
      {Ap.Note.empty with
       id         = "https://example.com/1" |> Uri.of_string;
       sensitive  = true;
       content_map= ["de","Inhalt"];
       summary_map= ["de","⚠️ Summary"];
       url        = ["https://Seppo.mro.name/demo" |> Uri.of_string];
      } in
    let base = Uri.empty in
    let minify = false in
    n
    |> Ap.Note.diluviate
    |> As2_vocab.Encode.note ~base |> Ezjsonm.value_to_string ~minify
    |> Assrt.equals_string
      __LOC__ {|{
  "type": "Note",
  "id": "https://example.com/1",
  "attributedTo": "",
  "mediaType": "text/html; charset=utf8",
  "contentMap": {
    "de": "<a href='https://seppo.mro.name/demo'>https://seppo.mro.name/demo</a><br/>\n<br/>\nInhalt"
  },
  "sensitive": true,
  "summaryMap": {
    "de": "⚠️ Summary"
  },
  "url": "https://example.com/1"
}|};
    assert true

  let tc_note_to_plain () =
(*
test/data/ap/inbox/create/note/note-OZcAekXDY1A.json
*)
    ()
end

let tc_create_follow_json () =
  let to_actor   = "http://example.com/to/actor" |> Uri.of_string
  and from_actor = "http://example.com/from/actor" |> Uri.of_string
  and to_inbox   = "http://example.com/to/inbox" |> Uri.of_string
  and base       = "http://example.com/from/" |> Uri.of_string
  and tnow       = Ptime.of_date_time ((2024,1,2), ((3,4,5),60*60)) |> Option.value ~default:Ptime.max
  and minify     = false in
  let _fo = to_actor |> Ap.Following.make ~tnow ~me:from_actor ~inbox:to_inbox in
  _fo
  |> As2_vocab.Encode.follow ~base
  |> 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": "Follow",
  "id": "http://example.com/from/actor#subscribe",
  "actor": "http://example.com/from/actor",
  "endTime": "2024-04-03T02:04:05Z",
  "object": "http://example.com/to/actor"
}|}

let tc_reject_json () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_reject_json");
  let reject me id =
    `O [("@context", `String "https://www.w3.org/ns/activitystreams");
        ("type", `String "Reject");
        ("actor", `String (me |> Uri.to_string));
        ("object", `String (id |> Uri.to_string))]
  in
  let me = "https://example.com/alice" |> Uri.of_string in
  let id = match "data/create.note.mastodon2.json" |> File.in_channel Ezjsonm.from_channel with
    | `O (_ :: ("id", `String id) :: _) -> id |> Uri.of_string
    | _ ->  Uri.empty in
  id |> Uri.to_string |> A.equals_string __LOC__ {|https://bewegung.social/users/mro/statuses/111561416759041219/activity|};
  id
  |> reject me
  |> Ezjsonm.value_to_string
  |> 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"}|};
  assert true

let tc_subscribed () =
  Logr.info (fun m -> m "%s" __LOC__);
  for i = 0 to 10 do
    try i |> Printf.sprintf "/tmp/%d.xml" |> Unix.unlink
    with | Unix.Unix_error(Unix.ENOENT, "unlink", _) -> ()
  done ;
  let cdb = Mapcdb.Cdb "data/2024-04-30-131146-subscribed.cdb" in
  cdb
  |> Mapcdb.fold_left (fun c _ -> 1 + c) 0
  |> A.equals_int __LOC__ 52;
  let r = Ap.Followers.Atom.rule in
  r.target |> A.equals_string __LOC__ "activitypub/subscribers/index.xml";
  (match Ap.Followers.Atom.of_cdb
           ~cdb
           ~base:("https://example.com/" |> Uri.of_string)
           ~title:"My Title"
           ~xsl:(Rfc4287.xsl "my.xsl" "./tmp/noop")
           ~rel:(Some Rfc4287.Link.subscribers)
           ~page_size:5
           "./tmp/" with
  | Error _ -> failwith __LOC__
  | Ok s -> s)
  |> A.equals_string __LOC__ "./tmp/10.xml";
  "./tmp/0.xml" |> File.to_string |> A.equals_string __LOC__ {|<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="../../themes/current/my.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://example.com/">
  <title>My Title</title>
  <id>0.xml</id>
  <link rel="self" href="0.xml" title="1"/>
  <link rel="first" href="." title="last"/>
  <link rel="last" href="0.xml" title="1"/>
  <link rel="previous" href="1.xml" title="2"/>
  <link rel="subscribers" href="https://social.wohlfarth.name/users/alain" title=""/>
  <link rel="subscribers" href="https://hci.social/users/sigchi" title=""/>
  <link rel="subscribers" href="https://fosstodon.org/users/nlnetlabs" title=""/>
  <link rel="subscribers" href="https://mastodon.xyz/users/NGIZero" title=""/>
  <link rel="subscribers" href="https://w3c.social/users/w3c" title=""/>
</feed>|};
  "./tmp/10.xml" |> File.to_string |> A.equals_string __LOC__ {|<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="../../themes/current/my.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://example.com/">
  <title>My Title</title>
  <id>10.xml</id>
  <link rel="self" href="10.xml" title="11"/>
  <link rel="first" href="." title="last"/>
  <link rel="last" href="0.xml" title="1"/>
  <link rel="previous" href="11.xml" title="12"/>
  <link rel="next" href="9.xml" title="10"/>
  <link rel="subscribers" href="https://mas.to/users/leostera" title=""/>
  <link rel="subscribers" href="https://social.network.europa.eu/users/EU_Commission" title="European Commission" rfc7565="acct:EU_Commission@social.network.europa.eu"/>
</feed>|};
  "?" |> A.equals_string __LOC__ "?"

let tc_to_rfc4287_0 () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "to_rfc4287_0");
  let now = ((2024,3,19), ((1,2,3),60*60)) |> Ptime.of_date_time
  and tz = Timedesc.Time_zone.utc in
  let n = {Ap.Note.empty with
           id        = "https://example.com/o/42" |> Uri.of_string;
           attributed_to = "https://example.com/a/23" |> Uri.of_string;
           content_map   = ["en","Some Content"];
           published = now;
           summary_map   = ["en","No content warning"];
          } in
  let now = now |> Option.value ~default:Ptime.epoch in
  let a = n |> Ap.Note.to_rfc4287 ~tz ~now in
  a.id |> Uri.to_string |> Assrt.equals_string __LOC__ "https://example.com/o/42";
  a.author.uri |> Option.value ~default:Uri.empty  |> Uri.to_string |> Assrt.equals_string __LOC__ "https://example.com/a/23";
  let Rfc3339.T t = a.published in
  t |> Assrt.equals_string __LOC__ "2024-03-19T00:02:03Z";
  a.title |> Assrt.equals_string __LOC__ "No content warning";
  a.content |> Assrt.equals_string __LOC__ "Some Content";
  ()

let tc_to_rfc4287_loaded () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "to_rfc4287_loaded");
  let load_note fn =
    let fn = "data/ap/inbox/create/note/" ^ fn in
    fn
    |> File.in_channel
      (fun ic ->
         match ic |> Ezjsonm.from_channel |> As2_vocab.Activitypub.Decode.obj with
         | Error _ -> failwith "failed to load note"
         | Ok o -> match o with
           | `Create { obj = `Note obj; _ } -> obj
           | _ -> failwith "strange type")
  in
  let now = ((2024,3,19), ((1,2,3),60*60)) |> Ptime.of_date_time |> Option.get
  and tz = Timedesc.Time_zone.utc
  and base = "https://example.com/fe/" |> Uri.of_string
  and attr = [ ((Xmlm.ns_xmlns,"xmlns"),Xml.ns_a);
               ((Xmlm.ns_xmlns,"thr")  ,Xml.ns_thr);
               ((Xmlm.ns_xmlns,"as")     ,Xml.ns_as);
               ((Xmlm.ns_xmlns,"wf")   ,Xml.ns_rfc7033); ] in
  let apu = load_note "note-Hjcb9bqwCgk.json" in
  let ato = apu |> Ap.Note.to_rfc4287 ~tz ~now in
  ato.author.name |> Assrt.equals_string __LOC__ "https://mastodon.social/users/nicholas_saunders";
  ato.author.uri |> Option.value ~default:Uri.empty  |> Uri.to_string |> Assrt.equals_string __LOC__ "https://mastodon.social/users/nicholas_saunders";
  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.

I'm inferring that this has something to with the Goat, but have no idea.  Guess I'll never know what it was about.

[1]: https://mastodon.social/tags/alaqsaflood|};
  let buf = 1024 |> Buffer.create in
  buf |> Xml.to_buf (ato |> Rfc4287.Entry.to_atom ~attr ~base);
  buf |> Buffer.to_bytes |> Bytes.to_string |> Assrt.equals_string __LOC__ {|<?xml version="1.0"?>
<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">
    <id>https://mastodon.social/users/nicholas_saunders/statuses/112615088973179734</id>
    <title type="text"></title>
    <updated>2024-06-14T12:58:07Z</updated>
    <published>2024-06-14T12:58:07Z</published>
    <as:sensitive>false</as:sensitive>
    <author>
      <name>https://mastodon.social/users/nicholas_saunders</name>
      <wf:uri></wf:uri>
      <uri>https://mastodon.social/users/nicholas_saunders</uri></author>
    <link rel="self" href="https://mastodon.social/users/nicholas_saunders/statuses/112615088973179734"/>
    <thr:in-reply-to ref="https://floss.social/users/wschenk/statuses/112615060106830322"/>
    <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.

I'm inferring that this has something to with the Goat, but have no idea.  Guess I'll never know what it was about.

[1]: https://mastodon.social/tags/alaqsaflood</content>
  </entry>|};
  ();
  let apu : As2_vocab.Types.note = load_note "note-OiqQGte9TSY.json" in
  (match apu.in_reply_to with
   | [ u ] -> u |> Uri.to_string |> Assrt.equals_string __LOC__ {|https://infosec.exchange/users/womble/statuses/112948594753912420|}
   | _ -> failwith __LOC__);
  let ato : Rfc4287.Entry.t = apu |> Ap.Note.to_rfc4287 ~tz ~now in
  apu.in_reply_to |> List.length |> Assrt.equals_int __LOC__ 1;
  ato.in_reply_to |> List.length |> Assrt.equals_int __LOC__ (apu.in_reply_to |> List.length);
  buf |> Buffer.clear;
  buf |> Xml.to_buf (ato |> Rfc4287.Entry.to_atom ~attr ~base);
  buf |> Buffer.to_bytes |> Bytes.to_string |> Assrt.equals_string __LOC__ {|<?xml version="1.0"?>
<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">
    <id>https://benjojo.co.uk/u/benjojo/h/89L5knBH4TGG4qY4VT</id>
    <title type="text"></title>
    <updated>2024-08-12T10:40:45Z</updated>
    <published>2024-08-12T10:40:45Z</published>
    <as:sensitive>false</as:sensitive>
    <author>
      <name>benjojo</name>
      <wf:uri>acct:benjojo@benjojo.co.uk</wf:uri>
      <uri>https://benjojo@benjojo.co.uk/u/benjojo</uri></author>
    <link rel="self" href="https://benjojo.co.uk/u/benjojo/h/89L5knBH4TGG4qY4VT"/>
    <thr:in-reply-to ref="https://infosec.exchange/users/womble/statuses/112948594753912420"/>
    <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>
  </entry>|};
  ()

let tc_create_note () =
  let base = "https://example.org/" |> Uri.of_string in
  let n = {Ap.Note.empty with
           id = "note-42" |> Uri.of_string |> Http.reso ~base;
           attributed_to = "alice" |> Uri.of_string |> Http.reso ~base;
           to_ = [As2_vocab.Constants.ActivityStreams.public];
           cc = ["activitypub/subscribers/index.jsa" |> Uri.of_string |> Http.reso ~base]} in
  let c = n |> Ap.Note.Create.make in
  c.to_
  |> List.map Uri.to_string |> Astring.String.concat ~sep:" "
  |> check string __LOC__ "https://www.w3.org/ns/activitystreams#Public";
  c.cc
  |> List.map Uri.to_string |> Astring.String.concat ~sep:" "
  |> check string __LOC__ "https://example.org/activitypub/subscribers/index.jsa";
  c.obj.to_
  |> List.map Uri.to_string |> Astring.String.concat ~sep:" "
  |> check string __LOC__ "https://www.w3.org/ns/activitystreams#Public";
  c.obj.cc
  |> List.map Uri.to_string |> Astring.String.concat ~sep:" "
  |> check string __LOC__ "https://example.org/activitypub/subscribers/index.jsa";
  c
  |> As2_vocab.Encode.(create ~lang:As2_vocab.Constants.ActivityStreams.und ~base (note ~base))
  |> Ezjsonm.value_to_string ~minify:false
  |> A.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": "Create",
  "id": "https://example.org/note-42#Create",
  "actor": "https://example.org/alice",
  "to": "https://www.w3.org/ns/activitystreams#Public",
  "cc": "https://example.org/activitypub/subscribers/index.jsa",
  "directMessage": false,
  "object": {
    "type": "Note",
    "id": "https://example.org/note-42",
    "attributedTo": "https://example.org/alice",
    "to": "https://www.w3.org/ns/activitystreams#Public",
    "cc": "https://example.org/activitypub/subscribers/index.jsa",
    "mediaType": "text/html; charset=utf8",
    "sensitive": false
  }
}|};
  ()

let tc_decode_create_notes () =
  Logr.info (fun m -> m "%s" __LOC__);
  let dir = "data/ap/inbox/create/note/" in
  let l : As2_vocab.Types.note list =
    dir |> File.fold_dir
      (fun init f ->
         let f = dir ^ f in
         try
           let j = f |> File.in_channel Ezjsonm.from_channel in
           (match j |> As2_vocab.Activitypub.Decode.obj with
            | Ok `Update { obj = `Note obj; _ }
            | Ok `Create { obj = `Note obj; _ } -> obj :: init
            | _ ->
              Logr.debug (fun m -> m "%s %s no!" "decode_create_notes" f);
              init),true
         with
         | e ->
           Logr.debug (fun m -> m "%s %s ouch! %s" "decode_create_notes" f (Printexc.to_string e));
           init,true)
      [] in
  l |> List.length |> Assrt.equals_int __LOC__ 204;
  let now = ((2024,3,19), ((1,2,3),60*60)) |> Ptime.of_date_time |> Option.value ~default:Ptime.epoch in
  let tz = Timedesc.Time_zone.make "Europe/Amsterdam" |> Option.value ~default:Timedesc.Time_zone.utc in
  let l = l |> List.map (Ap.Note.to_rfc4287 ~tz ~now) in
  l |> List.length |> Assrt.equals_int __LOC__ 204;
  let l = l |> List.sort Rfc4287.Entry.compare in
  let l = l |> List.filteri (fun i _ -> i < 2) in
  let x = l |> Rfc4287.Feed.to_atom
            ~base:(Uri.of_string "http://example.com/")
            ~self:(Uri.of_string "seppo.cgi/p-37/")
            ~prev:None
            ~next:None
            ~first:(Uri.of_string "seppo.cgi/p-37/")
            ~last:(Uri.of_string "seppo.cgi/p-0/")
            ~title:"ti"
            ~updated:(Rfc3339.T "2024-06-19 16:15:14+02:00")
            ~lang:(Rfc4287.Rfc4646 "de")
            ~author:{Rfc4287.Person.empty with
                     name = "Alice";
                     uri  = Some ("alice@example.com" |> Uri.of_string)} in
  let fn = "./tmp/as2_vocab_text.dat" in
  (try fn |> Unix.unlink with _ -> ());
  fn |> File.out_channel_replace (x |> Xml.to_chan ~xsl:(Rfc4287.xsl "posts.xsl" fn));
  (match fn |> File.cat with
   | Error _ -> failwith __LOC__
   | Ok s -> s)
  |> Assrt.equals_string __LOC__ {|<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="../../themes/current/posts.xsl"?>
<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/">
  <id>http://example.com/seppo.cgi/p-37/</id>
  <title type="text">ti</title>
  <updated>2024-06-19 16:15:14+02:00</updated>
  <generator uri="Seppo.mro.name">Seppo - Personal Social Web</generator>
  <link rel="self" href="seppo.cgi/p-37/" title="38"/>
  <link rel="first" href="seppo.cgi/p-37/" title="last"/>
  <link rel="last" href="seppo.cgi/p-0/" title="1"/>
  <entry xml:lang="en">
    <id>https://mastodon.social/users/nicholas_saunders/statuses/112608234771751001</id>
    <title type="text"></title>
    <updated>2024-06-13T09:55:01+02:00</updated>
    <published>2024-06-13T09:55:01+02:00</published>
    <as:sensitive>false</as:sensitive>
    <author>
      <name>https://mastodon.social/users/nicholas_saunders</name>
      <wf:uri></wf:uri>
      <uri>https://mastodon.social/users/nicholas_saunders</uri></author>
    <link rel="self" href="https://mastodon.social/users/nicholas_saunders/statuses/112608234771751001"/>
    <thr:in-reply-to ref="https://infosec.exchange/users/SpaceLifeForm/statuses/112608204971110992"/>
    <content type="text">@SpaceLifeForm@infosec.exchange @RickiTarr@beige.party I'll be here all day @SpaceLifeForm@infosec.exchange :)

(not really.)</content>
  </entry>
  <entry xml:lang="en">
    <id>https://mastodon.social/users/nicholas_saunders/statuses/112606941257663038</id>
    <title type="text"></title>
    <updated>2024-06-13T04:26:03+02:00</updated>
    <published>2024-06-13T04:26:03+02:00</published>
    <as:sensitive>false</as:sensitive>
    <author>
      <name>https://mastodon.social/users/nicholas_saunders</name>
      <wf:uri></wf:uri>
      <uri>https://mastodon.social/users/nicholas_saunders</uri></author>
    <link rel="self" href="https://mastodon.social/users/nicholas_saunders/statuses/112606941257663038"/>
    <thr:in-reply-to ref="https://mastodon.social/users/thejapantimes/statuses/112606642206596379"/>
    <content type="text">@thejapantimes@mastodon.social so then #hamas[1] didn't accept the offer, but rather seeks different terms.

[1]: https://mastodon.social/tags/hamas</content>
  </entry>
  </feed>|}

let () =
  run
    "seppo_suite" [
    __FILE__ , [
      "set_up",                    `Quick, set_up;
      "tc_follower_state",         `Quick, tc_follower_state;
      "tc_inbox_follow",           `Quick, tc_inbox_follow;
      "tc_inbox_unfollow",         `Quick, tc_inbox_unfollow;
      "tc_followers_page_json_raw",`Quick, tc_followers_page_json_raw;
      "tc_followers_page_json",    `Quick, tc_followers_page_json;
      "tc_followers_json",         `Quick, tc_followers_json;
      "tc_followers_cdb_json",     `Quick, tc_followers_cdb_json;
      "Note.tc_of_rfc4287",        `Quick, Note.tc_of_rfc4287;
      "Note.tc_create_note_json",  `Quick, Note.tc_create_note_json;
      "Note.tc_diluviate",         `Quick, Note.tc_diluviate;
      "Note.tc_note_to_plain",     `Quick, Note.tc_note_to_plain;
      "tc_create_follow_json",     `Quick, tc_create_follow_json;
      "tc_reject_json",            `Quick, tc_reject_json;
      "tc_subscribed",             `Quick, tc_subscribed;
      "tc_to_rfc4287_0",           `Quick, tc_to_rfc4287_0;
      "tc_to_rfc4287_loaded",      `Quick, tc_to_rfc4287_loaded;
      "tc_create_note",            `Quick, tc_create_note;
      "tc_decode_create_notes",    `Quick, tc_decode_create_notes;
    ]
  ];
  assert true
