an atproto pds written in F# (.NET 9) 馃
pds fsharp giraffe dotnet atproto
at main 5.7 kB view raw
1module Handlers.Tests 2 3open System 4open System.IO 5open System.Text 6open System.Text.Json 7open System.Threading.Tasks 8open System.Collections.Generic 9open Xunit 10open Microsoft.AspNetCore.Http 11open Giraffe 12open PDSharp.Core.Config 13open PDSharp.Core.BlockStore 14open PDSharp.Core 15open PDSharp.Core.SqliteStore 16open PDSharp.Core.Auth 17 18type MockAccountStore() = 19 let mutable accounts = Map.empty<string, Account> 20 21 interface IAccountStore with 22 member _.CreateAccount(account) = async { 23 if accounts.ContainsKey account.Did then 24 return Error "Exists" 25 else 26 accounts <- accounts.Add(account.Did, account) 27 return Ok() 28 } 29 30 member _.GetAccountByHandle(handle) = async { 31 return accounts |> Map.tryPick (fun _ v -> if v.Handle = handle then Some v else None) 32 } 33 34 member _.GetAccountByDid did = async { return accounts.TryFind did } 35 36type MockBlockStore() = 37 let mutable blocks = Map.empty<string, byte[]> 38 39 interface IBlockStore with 40 member _.Put(data) = async { 41 let hash = Crypto.sha256 data 42 let cid = Cid.FromHash hash 43 blocks <- blocks.Add(cid.ToString(), data) 44 return cid 45 } 46 47 member _.Get cid = async { return blocks.TryFind(cid.ToString()) } 48 member _.Has cid = async { return blocks.ContainsKey(cid.ToString()) } 49 50 member _.GetAllCidsAndData() = async { 51 return 52 blocks 53 |> Map.toList 54 |> List.choose (fun (k, v) -> Cid.TryParse k |> Option.map (fun c -> (c, v))) 55 } 56 57type MockRepoStore() = 58 let mutable repos = Map.empty<string, RepoRow> 59 60 interface IRepoStore with 61 member _.GetRepo(did) = async { return repos.TryFind did } 62 member _.SaveRepo(repo) = async { repos <- repos.Add(repo.did, repo) } 63 64type MockJsonSerializer() = 65 interface Giraffe.Json.ISerializer with 66 member _.SerializeToString x = JsonSerializer.Serialize x 67 member _.SerializeToBytes x = JsonSerializer.SerializeToUtf8Bytes x 68 member _.Deserialize<'T>(json : string) = JsonSerializer.Deserialize<'T> json 69 70 member _.Deserialize<'T>(bytes : byte[]) = 71 JsonSerializer.Deserialize<'T>(ReadOnlySpan bytes) 72 73 member _.DeserializeAsync<'T>(stream : Stream) = task { return! JsonSerializer.DeserializeAsync<'T>(stream) } 74 75 member _.SerializeToStreamAsync<'T> (x : 'T) (stream : Stream) = task { 76 do! JsonSerializer.SerializeAsync<'T>(stream, x) 77 } 78 79let mockContext (services : (Type * obj) list) (body : string) (query : Map<string, string>) = 80 let ctx = new DefaultHttpContext() 81 let serializer = MockJsonSerializer() 82 let allServices = (typeof<Giraffe.Json.ISerializer>, box serializer) :: services 83 84 let sp = 85 { new IServiceProvider with 86 member _.GetService(serviceType) = 87 allServices 88 |> List.tryPick (fun (t, s) -> if t = serviceType then Some s else None) 89 |> Option.toObj 90 } 91 92 ctx.RequestServices <- sp 93 94 if not (String.IsNullOrEmpty body) then 95 let stream = new MemoryStream(Encoding.UTF8.GetBytes(body)) 96 ctx.Request.Body <- stream 97 ctx.Request.ContentLength <- stream.Length 98 99 if not query.IsEmpty then 100 let dict = Dictionary<string, Microsoft.Extensions.Primitives.StringValues>() 101 102 for kvp in query do 103 dict.Add(kvp.Key, Microsoft.Extensions.Primitives.StringValues(kvp.Value)) 104 105 ctx.Request.Query <- QueryCollection dict 106 107 ctx 108 109[<Fact>] 110let ``Auth.createAccountHandler creates account successfully`` () = task { 111 let accountStore = MockAccountStore() 112 113 let config = { 114 PublicUrl = "https://pds.example.com" 115 DidHost = "did:web:pds.example.com" 116 JwtSecret = "secret" 117 SqliteConnectionString = "" 118 DisableWalAutoCheckpoint = false 119 BlobStore = Disk "blobs" 120 } 121 122 let services = [ typeof<AppConfig>, box config; typeof<IAccountStore>, box accountStore ] 123 124 let req : PDSharp.Handlers.Auth.CreateAccountRequest = { 125 handle = "alice.test" 126 email = Some "alice@test.com" 127 password = "password123" 128 inviteCode = None 129 } 130 131 let body = JsonSerializer.Serialize req 132 let ctx = mockContext services body Map.empty 133 let next : HttpFunc = fun _ -> Task.FromResult(None) 134 let! result = PDSharp.Handlers.Auth.createAccountHandler next ctx 135 Assert.Equal(200, ctx.Response.StatusCode) 136 137 let store = accountStore :> IAccountStore 138 let! accountOpt = store.GetAccountByHandle "alice.test" 139 Assert.True accountOpt.IsSome 140} 141 142[<Fact>] 143let ``Server.indexHandler returns HTML`` () = task { 144 let ctx = new DefaultHttpContext() 145 let next : HttpFunc = fun _ -> Task.FromResult(None) 146 let! result = PDSharp.Handlers.Server.indexHandler next ctx 147 Assert.Equal(200, ctx.Response.StatusCode) 148 Assert.Equal("text/html", ctx.Response.ContentType) 149} 150 151[<Fact>] 152let ``Repo.createRecordHandler invalid collection returns error`` () = task { 153 let blockStore = MockBlockStore() 154 let repoStore = MockRepoStore() 155 let keyStore = PDSharp.Handlers.SigningKeyStore() 156 let firehose = PDSharp.Handlers.FirehoseState() 157 158 let services = [ 159 typeof<IBlockStore>, box blockStore 160 typeof<IRepoStore>, box repoStore 161 typeof<PDSharp.Handlers.SigningKeyStore>, box keyStore 162 typeof<PDSharp.Handlers.FirehoseState>, box firehose 163 ] 164 165 let record = JsonSerializer.Deserialize<JsonElement> "{\"text\":\"hello\"}" 166 167 let req : PDSharp.Handlers.Repo.CreateRecordRequest = { 168 repo = "did:web:alice.test" 169 collection = "app.bsky.feed.post" 170 record = record 171 rkey = None 172 } 173 174 let body = JsonSerializer.Serialize(req) 175 let ctx = mockContext services body Map.empty 176 let next : HttpFunc = fun _ -> Task.FromResult(None) 177 let! result = PDSharp.Handlers.Repo.createRecordHandler next ctx 178 Assert.Equal(400, ctx.Response.StatusCode) 179}