using System.Net; using LlamaApp.HuggingFace; using Xunit; namespace LlamaApp.Tests; /// /// Unit tests for : the /// no-token short-circuit, the Bearer request shape, and the whoami-v2 JSON /// → mapping. /// public class HubUserInfoTests { // A whoami-v2 payload with the full documented structure — the nested // auth/orgs noise must be ignored and only id/name/avatarUrl mapped. private const string WhoAmIJson = """ { "auth": { "type": "accessToken", "accessToken": { "displayName": "cli", "role": "read", "fineGrained": { "scoped": [ { "entity": { "_id": "abc", "name": "ds", "type": "dataset" }, "permissions": [ "read" ] } ], "global": [], "canReadGatedRepos": true }, "createdAt": "2026-07-30T10:37:06.105Z" }, "expiresAt": "2026-07-30T10:37:06.105Z", "resource": { "sub": "65f1a2b3c4d5e6f7a8b9c0d1" } }, "type": "user", "id": "65f1a2b3c4d5e6f7a8b9c0d1", "name": "momo", "fullname": "Momo Dev", "email": "momo@example.com", "canPay": true, "billingMode": "prepaid", "avatarUrl": "https://cdn-avatars.huggingface.co/v1/production/uploads/abc/avatar.png", "periodEnd": 1, "emailVerified": true, "isPro": true, "orgs": [ { "type": "org", "id": "org-id", "name": "acme", "fullname": "Acme Inc", "email": "hi@acme.com", "canPay": true, "billingMode": "prepaid", "avatarUrl": "https://example.com/org.png", "periodEnd": 1, "plan": "team", "roleInOrg": "admin", "securityRestrictions": [], "resourceGroups": [ { "id": "rg", "name": "rg", "role": "admin" } ] } ] } """; // ----- No token --------------------------------------------------------- [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] public async Task WhoAmI_Without_Token_Returns_Null(string? token) { var info = await new HubClient(token).UserInfo.WhoAmI(); Assert.Null(info); } // ----- HTTP path (mock handler) ----------------------------------------- [Fact] public async Task WhoAmI_Sends_Bearer_Token_To_WhoAmI_V2_And_Maps_Response() { string? auth = null; Uri? requestUri = null; var client = new HttpClient(new StubHandler(req => { auth = req.Headers.Authorization?.ToString(); requestUri = req.RequestUri; return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(WhoAmIJson), }; })); var info = await new HubClient("hf_secret").UserInfo.WhoAmI(client); Assert.Equal("Bearer hf_secret", auth); Assert.Equal("https://huggingface.co/api/whoami-v2", requestUri?.ToString()); Assert.NotNull(info); Assert.Equal("65f1a2b3c4d5e6f7a8b9c0d1", info.Id); Assert.Equal("momo", info.Name); Assert.Equal("https://cdn-avatars.huggingface.co/v1/production/uploads/abc/avatar.png", info.AvatarUrl); } [Theory] [InlineData(HttpStatusCode.Unauthorized)] [InlineData(HttpStatusCode.Forbidden)] [InlineData(HttpStatusCode.InternalServerError)] public async Task WhoAmI_Non_Success_Status_Returns_Null(HttpStatusCode status) { var client = new HttpClient(new StubHandler(_ => new HttpResponseMessage(status))); var info = await new HubClient("hf_secret").UserInfo.WhoAmI(client); Assert.Null(info); } // ----- Parse ------------------------------------------------------------ [Fact] public void Parse_Missing_Fields_Map_To_Empty_Strings() { var info = HubClient.HubUserInfoClient.Parse("""{ "type": "user" }"""); Assert.NotNull(info); Assert.Equal("", info.Id); Assert.Equal("", info.Name); Assert.Equal("", info.AvatarUrl); } [Fact] public void Parse_Malformed_Json_Returns_Null() { Assert.Null(HubClient.HubUserInfoClient.Parse("not json")); } // ----- Stub ------------------------------------------------------------- /// Minimal HttpMessageHandler stub: returns a canned response and /// lets the test inspect the outgoing request inside the delegate (the /// request is disposed by the caller after SendAsync). private sealed class StubHandler(Func respond) : HttpMessageHandler { protected override Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) => Task.FromResult(respond(request)); } }