Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions tests/OpenCode.Sdk.Tests/Common/Errors/DeclaredErrorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System.Globalization;
using System.Net;
using System.Text;
using System.Text.Json;
using OpenCode.Sdk.Models;
using OpenCode.Sdk.Tests.Common.Support;
using OpenCode.Sdk.Tests.Harness;
using OpenCode.Sdk.Tests.Support;

namespace OpenCode.Sdk.Tests.Common.Errors;

public sealed class DeclaredErrorTests
{
public static IEnumerable<Func<DeclaredErrorCase>> EveryDoor() =>
[
static () => new(
"worktree.create:400:InvalidRequestError",
new DeclaredErrorDoor(
nameof(WorktreesClient.CreateWorktreeAsync),
"worktree.create",
"post",
"/api/worktree",
static async (client, options) => await client.Worktrees.CreateWorktreeAsync(
new WorktreeCreateRequest { ProjectId = "prj_1", Directory = "/repo/feature" }, options)),
HttpStatusCode.BadRequest,
WireBodyData.InvalidRequestError,
"InvalidRequestError",
typeof(InvalidRequestError),
"#/paths/~1api~1worktree/post/responses/400/content/application~1json/schema/anyOf/1"),
];

[Test]
[MethodDataSource(nameof(EveryDoor))]
[DisplayName(nameof(Door_Should_Throw_The_Declared_Error) + "($row)")]
public async Task Door_Should_Throw_The_Declared_Error(DeclaredErrorCase row)
{
using var scenario = ContractScenario.Responding(row.Status, row.WireBody);

var exception = await Assert.That(async () => _ = await row.Door.Invoke(scenario.Client, null))
.Throws<OpenCodeApiException>();

await Assert.That(exception!.Status).IsEqualTo((int)row.Status);
await Assert.That(exception.Error?.GetType()).IsEqualTo(row.ErrorType);
}

[Test]
[MethodDataSource(nameof(EveryDoor))]
[DisplayName(nameof(Door_Should_Return_The_Declared_Error_On_NoThrow) + "($row)")]
public async Task Door_Should_Return_The_Declared_Error_On_NoThrow(DeclaredErrorCase row)
{
using var scenario = ContractScenario.Responding(row.Status, row.WireBody);

var response = await row.Door.Invoke(scenario.Client, OpenCodeRequestOptions.NoThrow);

await Assert.That(response.IsError).IsTrue();
await Assert.That(response.Status).IsEqualTo((int)row.Status);
await Assert.That(response.Error?.GetType()).IsEqualTo(row.ErrorType);
}

[Test]
[MethodDataSource(nameof(EveryDoor))]
[DisplayName(nameof(Every_Row_Should_Match_The_Pinned_Declaration) + "($row)")]
public async Task Every_Row_Should_Match_The_Pinned_Declaration(DeclaredErrorCase row)
{
using var document = JsonDocument.Parse(new FixtureLoader().LoadJson("Pinned.openapi.json"));
var operation = document.RootElement.GetProperty("paths").GetProperty(row.Door.Path)
.GetProperty(row.Door.HttpMethod);
await Assert.That(operation.GetProperty("operationId").GetString()).IsEqualTo(row.Door.OperationId);

var operationPointer = "#/paths/" + new StringBuilder(row.Door.Path).Replace("~", "~0").Replace("/", "~1")
+ "/" + row.Door.HttpMethod + "/responses/";
await Assert.That(row.JsonPointer).StartsWith(operationPointer);
var status = row.JsonPointer[operationPointer.Length..].Split('/')[0];
await Assert.That(status).IsEqualTo(((int)row.Status).ToString(CultureInfo.InvariantCulture));

var branch = ResolvePointer(document.RootElement, row.JsonPointer);
// GetProperty throws when the branch has no $ref, and a pinned $ref value is a JSON string, so GetString() is not null.
var reference = branch.GetProperty("$ref").GetString()!;
await Assert.That(reference).StartsWith("#/components/schemas/");
var component = ResolvePointer(document.RootElement, reference);
var tags = component.GetProperty("properties").GetProperty("_tag").GetProperty("enum");
await Assert.That(tags.GetArrayLength()).IsEqualTo(1);
await Assert.That(tags[0].GetString()).IsEqualTo(row.Tag);
}

private static JsonElement ResolvePointer(JsonElement root, string pointer)
{
var current = root;
foreach (var segment in pointer.Split('/').Skip(1))
{
var key = new StringBuilder(segment).Replace("~1", "/").Replace("~0", "~").ToString();
current = current.ValueKind == JsonValueKind.Array
? current[int.Parse(key, CultureInfo.InvariantCulture)]
: current.GetProperty(key);
}

return current;
}
}
15 changes: 15 additions & 0 deletions tests/OpenCode.Sdk.Tests/Common/Support/DeclaredErrorCase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Net;

namespace OpenCode.Sdk.Tests.Common.Support;

public readonly record struct DeclaredErrorCase(
string CaseId,
DeclaredErrorDoor Door,
HttpStatusCode Status,
string WireBody,
string Tag,
Type ErrorType,
string JsonPointer)
{
public override string ToString() => CaseId + " " + Door.MemberName;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace OpenCode.Sdk.Tests.Common.Support;

public sealed record DeclaredErrorDoor(
string MemberName,
string OperationId,
string HttpMethod,
string Path,
Func<OpenCodeClient, OpenCodeRequestOptions?, Task<OpenCodeResponse>> Invoke);
1 change: 1 addition & 0 deletions tests/OpenCode.Sdk.Tests/OpenCode.Sdk.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="../../spec/openapi.json" Link="Fixtures/Pinned/openapi.json" LogicalName="OpenCode.Sdk.Tests.Fixtures.Pinned.openapi.json" />
<EmbeddedResource Include="Fixtures/**/*.json" />
<EmbeddedResource Include="Fixtures/**/*.js" />
<EmbeddedResource Include="Fixtures/**/*.md" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,6 @@ public async Task CreateWorktreeAsync_Should_Throw_The_Declared_400_Worktree_Err
await Assert.That(error.Data.ForceRequired).IsTrue();
}

[Test]
public async Task CreateWorktreeAsync_Should_Throw_The_Other_Declared_400_Error()
{
using var scenario = ContractScenario.Responding(HttpStatusCode.BadRequest, WireBodyData.InvalidRequestError);

var exception = await Assert
.That(async () => _ = await scenario.Client.Worktrees.CreateWorktreeAsync(CreateRequest()))
.Throws<OpenCodeApiException>();

await Assert.That(exception!.Status).IsEqualTo(400);
await Assert.That(exception.Error).IsTypeOf<InvalidRequestError>();
}

[Test]
public async Task CreateWorktreeAsync_Should_Return_The_401_Error_On_The_NoThrow_Spine()
{
Expand Down
2 changes: 0 additions & 2 deletions tools/jscpd/tests.baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@
"16581e5dac5d2421": 1,
"171c27993e2f8380": 1,
"17537e2a94773132": 1,
"17ed203ca957f805": 1,
"18b20d9b8321f296": 1,
"1909d5d413d0fafa": 1,
"196f4000011d069a": 1,
Expand Down Expand Up @@ -347,7 +346,6 @@
"7eda9e729d5869ca": 1,
"7f8b8b1b7f8ac7c9": 1,
"7ffcd9e9160f6b45": 1,
"804568ff414ee4ab": 1,
"80cc7036ff095cb9": 1,
"80df9c5748306399": 1,
"813356530d4e26c5": 1,
Expand Down
Loading