an atproto pds written in F# (.NET 9) 馃
pds fsharp giraffe dotnet atproto
at main 4.7 kB view raw
1module Auth.Tests 2 3open Xunit 4open PDSharp.Core.Auth 5open System 6open System.Collections.Concurrent 7 8/// Mock in-memory store for testing 9type VolatileAccountStore() = 10 let accounts = ConcurrentDictionary<string, Account>() 11 let handles = ConcurrentDictionary<string, string>() 12 13 interface IAccountStore with 14 member _.CreateAccount(account : Account) = async { 15 if handles.ContainsKey(account.Handle) then 16 return Error "Handle already taken" 17 elif accounts.ContainsKey(account.Did) then 18 return Error "Account already exists" 19 else 20 accounts.TryAdd(account.Did, account) |> ignore 21 handles.TryAdd(account.Handle, account.Did) |> ignore 22 return Ok() 23 } 24 25 member _.GetAccountByHandle(handle : string) = async { 26 match handles.TryGetValue(handle) with 27 | true, did -> 28 match accounts.TryGetValue(did) with 29 | true, acc -> return Some acc 30 | _ -> return None 31 | _ -> return None 32 } 33 34 member _.GetAccountByDid(did : string) = async { 35 match accounts.TryGetValue(did) with 36 | true, acc -> return Some acc 37 | _ -> return None 38 } 39 40[<Fact>] 41let ``Password hashing produces salt$hash format`` () = 42 let hash = hashPassword "mypassword" 43 Assert.Contains("$", hash) 44 let parts = hash.Split('$') 45 Assert.Equal(2, parts.Length) 46 47[<Fact>] 48let ``Password verification succeeds for correct password`` () = 49 let hash = hashPassword "mypassword" 50 Assert.True(verifyPassword "mypassword" hash) 51 52[<Fact>] 53let ``Password verification fails for wrong password`` () = 54 let hash = hashPassword "mypassword" 55 Assert.False(verifyPassword "wrongpassword" hash) 56 57[<Fact>] 58let ``Password verification fails for invalid hash format`` () = 59 Assert.False(verifyPassword "password" "invalidhash") 60 Assert.False(verifyPassword "password" "") 61 62[<Fact>] 63let ``JWT access token creation and validation`` () = 64 let secret = "test-secret-key-minimum-32-chars!" 65 let did = "did:web:test.example" 66 67 let token = createAccessToken secret did 68 69 let parts = token.Split('.') 70 Assert.Equal(3, parts.Length) 71 72 match validateToken secret token with 73 | Valid(extractedDid, tokenType, _) -> 74 Assert.Equal(did, extractedDid) 75 Assert.Equal(Access, tokenType) 76 | Invalid reason -> Assert.Fail $"Token should be valid, got: {reason}" 77 78[<Fact>] 79let ``JWT refresh token has correct type`` () = 80 let secret = "test-secret-key-minimum-32-chars!" 81 let did = "did:web:test.example" 82 83 let token = createRefreshToken secret did 84 85 match validateToken secret token with 86 | Valid(_, tokenType, _) -> Assert.Equal(Refresh, tokenType) 87 | Invalid reason -> Assert.Fail $"Token should be valid, got: {reason}" 88 89[<Fact>] 90let ``JWT validation fails with wrong secret`` () = 91 let secret = "test-secret-key-minimum-32-chars!" 92 let wrongSecret = "wrong-secret-key-minimum-32-chars!" 93 let did = "did:web:test.example" 94 95 let token = createAccessToken secret did 96 97 match validateToken wrongSecret token with 98 | Invalid _ -> Assert.True(true) 99 | Valid _ -> Assert.Fail "Token should be invalid with wrong secret" 100 101[<Fact>] 102let ``Account creation and lookup by handle`` () = 103 let store = VolatileAccountStore() 104 105 match 106 createAccount store "test.user" "password123" (Some "test@example.com") 107 |> Async.RunSynchronously 108 with 109 | Error msg -> Assert.Fail msg 110 | Ok account -> 111 Assert.Equal("test.user", account.Handle) 112 Assert.Equal("did:web:test.user", account.Did) 113 Assert.Equal(Some "test@example.com", account.Email) 114 115 let found = 116 (store :> IAccountStore).GetAccountByHandle "test.user" 117 |> Async.RunSynchronously 118 119 match found with 120 | None -> Assert.Fail "Account should be found" 121 | Some foundAcc -> Assert.Equal(account.Did, foundAcc.Did) 122 123[<Fact>] 124let ``Account creation fails for duplicate handle`` () = 125 let store = VolatileAccountStore() 126 127 createAccount store "duplicate.user" "password" None 128 |> Async.RunSynchronously 129 |> ignore 130 131 match createAccount store "duplicate.user" "password2" None |> Async.RunSynchronously with 132 | Error msg -> Assert.Contains("already", msg.ToLower()) 133 | Ok _ -> Assert.Fail "Should fail for duplicate handle" 134 135[<Fact>] 136let ``Account lookup by DID`` () = 137 let store = VolatileAccountStore() 138 139 match createAccount store "did.user" "password123" None |> Async.RunSynchronously with 140 | Error msg -> Assert.Fail msg 141 | Ok account -> 142 let found = 143 (store :> IAccountStore).GetAccountByDid account.Did |> Async.RunSynchronously 144 145 match found with 146 | None -> Assert.Fail "Account should be found by DID" 147 | Some foundAcc -> Assert.Equal(account.Handle, foundAcc.Handle)