chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MCPForUnity.Editor.Services.AssetGen.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Test double for <see cref="IHttpTransport"/>. Records every request it is handed and
|
||||
/// returns a canned response, so provider adapters can be exercised without a network.
|
||||
/// Match responses either with the <see cref="Handler"/> delegate (full control) or with
|
||||
/// <see cref="ByUrlSubstring"/> (first entry whose key is contained in the request URL).
|
||||
/// </summary>
|
||||
public sealed class FakeHttpTransport : IHttpTransport
|
||||
{
|
||||
public List<HttpRequestSpec> RecordedRequests { get; } = new List<HttpRequestSpec>();
|
||||
|
||||
/// <summary>Highest-priority responder; return null to fall through to <see cref="ByUrlSubstring"/>.</summary>
|
||||
public Func<HttpRequestSpec, HttpResult> Handler { get; set; }
|
||||
|
||||
/// <summary>Canned responses keyed by a substring expected to appear in the request URL.</summary>
|
||||
public Dictionary<string, HttpResult> ByUrlSubstring { get; } = new Dictionary<string, HttpResult>();
|
||||
|
||||
public Task<HttpResult> SendAsync(HttpRequestSpec spec, CancellationToken ct)
|
||||
{
|
||||
RecordedRequests.Add(spec);
|
||||
|
||||
HttpResult result = Handler?.Invoke(spec);
|
||||
|
||||
if (result == null && spec?.Url != null)
|
||||
{
|
||||
foreach (var kv in ByUrlSubstring)
|
||||
{
|
||||
if (spec.Url.IndexOf(kv.Key, StringComparison.Ordinal) >= 0)
|
||||
{
|
||||
result = kv.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
result = new HttpResult
|
||||
{
|
||||
Status = 500,
|
||||
IsSuccess = false,
|
||||
Text = "FakeHttpTransport: no canned response matched " + (spec?.Url ?? "<null>")
|
||||
};
|
||||
}
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46a96ea76bac45cab5025a37f56ba671
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MCPForUnity.Editor.Services.AssetGen.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Transport-agnostic description of a single HTTP request. Provider adapters build one
|
||||
/// of these and hand it to an <see cref="IHttpTransport"/>; this keeps adapters free of
|
||||
/// any direct dependency on UnityWebRequest so they can be unit-tested without a network.
|
||||
/// </summary>
|
||||
public sealed class HttpRequestSpec
|
||||
{
|
||||
public string Method;
|
||||
public string Url;
|
||||
public Dictionary<string, string> Headers = new Dictionary<string, string>();
|
||||
public byte[] Body;
|
||||
public string ContentType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82093efd40c44dc3bd83e481484ef827
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace MCPForUnity.Editor.Services.AssetGen.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Transport-agnostic result of an <see cref="HttpRequestSpec"/>. <see cref="Status"/> is
|
||||
/// the numeric HTTP status code; <see cref="IsSuccess"/> reflects the transport's own view
|
||||
/// of success (e.g. UnityWebRequest.Result.Success), which adapters combine with their own
|
||||
/// body-level checks.
|
||||
/// </summary>
|
||||
public sealed class HttpResult
|
||||
{
|
||||
public int Status;
|
||||
public byte[] Body;
|
||||
public string Text;
|
||||
public bool IsSuccess;
|
||||
|
||||
/// <summary>True when the transport reports success or the status code is 2xx.</summary>
|
||||
public bool Ok => IsSuccess || (Status >= 200 && Status < 300);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 507ef9b8ac9d4f42be51a3bc9203eeff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MCPForUnity.Editor.Services.AssetGen.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// The HTTP seam that provider adapters depend on. Production uses
|
||||
/// <see cref="UnityWebRequestTransport"/>; tests inject <see cref="FakeHttpTransport"/> so
|
||||
/// adapter request/response shaping can be verified without touching the network.
|
||||
/// </summary>
|
||||
public interface IHttpTransport
|
||||
{
|
||||
Task<HttpResult> SendAsync(HttpRequestSpec spec, CancellationToken ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 746824fdb60a436b880f22fee8cdb177
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace MCPForUnity.Editor.Services.AssetGen.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Production <see cref="IHttpTransport"/> backed by UnityWebRequest. Must be invoked on the
|
||||
/// Unity main thread (the asset-gen job manager guarantees this in Phase 3). The send is
|
||||
/// awaited via a <see cref="TaskCompletionSource{T}"/> wired to the async op's completed
|
||||
/// callback, so the call never blocks the editor loop.
|
||||
/// </summary>
|
||||
public sealed class UnityWebRequestTransport : IHttpTransport
|
||||
{
|
||||
public Task<HttpResult> SendAsync(HttpRequestSpec spec, CancellationToken ct)
|
||||
{
|
||||
if (spec == null) throw new ArgumentNullException(nameof(spec));
|
||||
|
||||
var tcs = new TaskCompletionSource<HttpResult>();
|
||||
|
||||
var request = new UnityWebRequest(spec.Url, spec.Method ?? UnityWebRequest.kHttpVerbGET)
|
||||
{
|
||||
downloadHandler = new DownloadHandlerBuffer()
|
||||
};
|
||||
if (spec.Body != null)
|
||||
{
|
||||
request.uploadHandler = new UploadHandlerRaw(spec.Body);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(spec.ContentType))
|
||||
{
|
||||
request.SetRequestHeader("Content-Type", spec.ContentType);
|
||||
}
|
||||
if (spec.Headers != null)
|
||||
{
|
||||
foreach (var kv in spec.Headers)
|
||||
{
|
||||
request.SetRequestHeader(kv.Key, kv.Value);
|
||||
}
|
||||
}
|
||||
|
||||
CancellationTokenRegistration ctReg = default;
|
||||
if (ct.CanBeCanceled)
|
||||
{
|
||||
ctReg = ct.Register(() =>
|
||||
{
|
||||
try { request.Abort(); } catch { /* ignore */ }
|
||||
tcs.TrySetCanceled();
|
||||
});
|
||||
}
|
||||
|
||||
var op = request.SendWebRequest();
|
||||
op.completed += _ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = new HttpResult
|
||||
{
|
||||
Status = (int)request.responseCode,
|
||||
Body = request.downloadHandler?.data,
|
||||
Text = request.downloadHandler?.text,
|
||||
IsSuccess = request.result == UnityWebRequest.Result.Success
|
||||
};
|
||||
tcs.TrySetResult(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
tcs.TrySetException(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctReg.Dispose();
|
||||
request.Dispose();
|
||||
}
|
||||
};
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 333575db1d8f487a9f2d32fa78dbb009
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user