using System.Text; using System.Threading; using MCPForUnity.Editor.Services.AssetGen.Http; using MCPForUnity.Editor.Services.AssetGen.Providers; using NUnit.Framework; namespace MCPForUnityTests.Editor.AssetGen { /// /// Exercises the Tripo adapter against a — no network. /// Asserts request shaping (endpoint, method, auth scheme, body) and poll-state mapping. /// The bearer secret is never asserted beyond the "Bearer " scheme prefix. /// public class TripoAdapterTests { private static HttpResult Json(string json, int status = 200) => new HttpResult { Status = status, IsSuccess = status >= 200 && status < 300, Text = json, Body = Encoding.UTF8.GetBytes(json) }; [Test] public void Submit_PostsTaskEndpoint_WithBearerHeader_AndReturnsTaskId() { var http = new FakeHttpTransport { Handler = _ => Json("{\"code\":0,\"data\":{\"task_id\":\"abc123\"}}") }; var adapter = new TripoAdapter(); var req = new ModelGenRequest { Provider = "tripo", Mode = "text", Prompt = "a red dragon" }; string taskId = adapter.SubmitAsync(req, "tsk_secret_value", http, CancellationToken.None) .GetAwaiter().GetResult(); Assert.AreEqual("abc123", taskId); Assert.AreEqual(1, http.RecordedRequests.Count); HttpRequestSpec rec = http.RecordedRequests[0]; StringAssert.Contains("/v2/openapi/task", rec.Url); Assert.AreEqual("POST", rec.Method); Assert.IsTrue(rec.Headers.ContainsKey("Authorization")); StringAssert.StartsWith("Bearer ", rec.Headers["Authorization"]); string body = Encoding.UTF8.GetString(rec.Body); StringAssert.Contains("a red dragon", body); StringAssert.Contains("text_to_model", body); } [Test] public void Poll_MapsSuccess_WithDownloadUrl() { var http = new FakeHttpTransport { Handler = _ => Json( "{\"code\":0,\"data\":{\"status\":\"success\",\"progress\":100," + "\"output\":{\"pbr_model\":\"https://cdn.tripo3d.ai/model.glb\"}}}") }; var adapter = new TripoAdapter(); ProviderPollResult res = adapter.PollAsync("abc123", "tsk_secret_value", http, CancellationToken.None) .GetAwaiter().GetResult(); Assert.AreEqual(ProviderPollState.Succeeded, res.State); Assert.AreEqual("https://cdn.tripo3d.ai/model.glb", res.DownloadUrl); } [Test] public void Poll_MapsRunning_WithProgress() { var http = new FakeHttpTransport { Handler = _ => Json("{\"code\":0,\"data\":{\"status\":\"running\",\"progress\":42}}") }; var adapter = new TripoAdapter(); ProviderPollResult res = adapter.PollAsync("abc123", "k", http, CancellationToken.None) .GetAwaiter().GetResult(); Assert.AreEqual(ProviderPollState.Running, res.State); Assert.AreEqual(0.42f, res.Progress, 0.001f); } [Test] public void Poll_MapsFailed_WithError() { var http = new FakeHttpTransport { Handler = _ => Json("{\"code\":0,\"data\":{\"status\":\"failed\",\"progress\":0}}") }; var adapter = new TripoAdapter(); ProviderPollResult res = adapter.PollAsync("abc123", "k", http, CancellationToken.None) .GetAwaiter().GetResult(); Assert.AreEqual(ProviderPollState.Failed, res.State); Assert.IsNotEmpty(res.Error); } [Test] public void Submit_ImageMode_UsesImageToModel() { var http = new FakeHttpTransport { Handler = _ => Json("{\"code\":0,\"data\":{\"task_id\":\"img1\"}}") }; var adapter = new TripoAdapter(); var req = new ModelGenRequest { Provider = "tripo", Mode = "image", ImageUrl = "https://example.com/in.png" }; adapter.SubmitAsync(req, "k", http, CancellationToken.None).GetAwaiter().GetResult(); string body = Encoding.UTF8.GetString(http.RecordedRequests[0].Body); StringAssert.Contains("image_to_model", body); StringAssert.Contains("https://example.com/in.png", body); } [Test] public void Submit_ImageMode_LocalPathOnly_Throws() { // Tripo can't take a local image inline (no data-URI support, upload not wired) — it must // fail clearly rather than silently falling back to text mode. var adapter = new TripoAdapter(); var req = new ModelGenRequest { Provider = "tripo", Mode = "image", ImagePath = "/tmp/whatever.png" }; Assert.Throws(() => adapter.SubmitAsync(req, "k", new FakeHttpTransport(), CancellationToken.None).GetAwaiter().GetResult()); } } }