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
2 changes: 1 addition & 1 deletion docs/engineering/quality-gates.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ The lint job runs `dotnet run --file tools/opencode-tool.cs -- verify-clones`: t
`tools/jscpd/`, installed with the machine's Bun from its own lockfile, at a 50-token minimum over four
scopes. Hand-written `src/`, `tools/` and `tests/` are gated against the committed baselines in
`tools/jscpd/`: a clone a baseline does not hold fails the job, `tools/` also fails above 3%
duplicated lines, and `tests/` above 6%. The tests ceiling is the measurement plus 0.25 points,
duplicated lines, and `tests/` above 5.5%. The tests ceiling is the measurement plus 0.25 points,
rounded up to the next 0.5%, and it only ever falls: a change that removes test clones lowers it
to that value when it is lower than the current ceiling. Generated output is
reported, not gated. The generator's manifest decides which `src/` files are generated, so the split
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using OpenCode.Sdk.Tests.Harness;
using OpenCode.Sdk.Tests.Integrations;
using OpenCode.Sdk.Tests.Support;
using OpenCode.Sdk.Tests.Vcs;
using OpenCode.Sdk.Tests.Worktrees;

namespace OpenCode.Sdk.Tests.Common.Errors;
Expand All @@ -27,6 +28,7 @@ .. ExperimentalDeclaredErrors.Rows(),
.. FormsDeclaredErrors.Rows(),
.. FileSystemDeclaredErrors.Rows(),
.. IntegrationsDeclaredErrors.Rows(),
.. VcsDeclaredErrors.Rows(),
];

[Test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -456,24 +456,6 @@ public async Task Carrier_Constructor_Should_Replay_A_Matching_Fixed_Outer_Marke
await Assert.That(roundTrip).IsEqualTo(json);
}

[Test]
public async Task Deserialize_Should_Reject_A_Numeric_String_Enum_Value()
{
var json = _fixtures.LoadJson("Serialization.numeric-diff-status.json");

_ = await Assert.That(() => _serializer.Deserialize<FileDiffInfo>(json)).Throws<JsonException>();
}

[Test]
public async Task Deserialize_Should_Map_A_Known_String_Enum_Wire_Value()
{
var json = _fixtures.LoadJson("Serialization.known-diff-status.json");

var diff = _serializer.Deserialize<FileDiffInfo>(json);

await Assert.That(diff.Status).IsEqualTo(FileDiffInfoStatus.Modified);
}

[Arguments("NaN", double.NaN)]
[Arguments("Infinity", double.PositiveInfinity)]
[Arguments("-Infinity", double.NegativeInfinity)]
Expand Down
102 changes: 0 additions & 102 deletions tests/OpenCode.Sdk.Tests/Vcs/VcsClientContractTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,31 +28,6 @@ await Assert.That(scenario.Requests.Single().RequestUri)
.IsEqualTo(new Uri("http://localhost:4096/api/vcs/status"));
}

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

var exception = await Assert
.That(async () => _ = await scenario.Client.Vcs.GetStatusAsync())
.Throws<OpenCodeApiException>();

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

[Test]
public async Task GetStatusAsync_Should_Return_The_401_Error_On_The_NoThrow_Spine()
{
using var scenario = ContractScenario.Responding(HttpStatusCode.Unauthorized, WireBodyData.UnauthorizedError);

var response = await scenario.Client.Vcs.GetStatusAsync(requestOptions: OpenCodeRequestOptions.NoThrow);

await Assert.That(response.IsError).IsTrue();
await Assert.That(response.Status).IsEqualTo(401);
await Assert.That(response.Error).IsTypeOf<UnauthorizedError>();
}

[Test]
public async Task ListBranchesAsync_Should_Return_The_Typed_Branches_With_Their_Location()
{
Expand Down Expand Up @@ -80,31 +55,6 @@ public async Task ListBranchesAsync_Should_Return_An_Empty_List_With_Its_Locatio
await Assert.That(response.Location.Directory).IsEqualTo(WireBodyData.ResolvedDirectory);
}

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

var exception = await Assert
.That(async () => _ = await scenario.Client.Vcs.ListBranchesAsync())
.Throws<OpenCodeApiException>();

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

[Test]
public async Task ListBranchesAsync_Should_Return_The_401_Error_On_The_NoThrow_Spine()
{
using var scenario = ContractScenario.Responding(HttpStatusCode.Unauthorized, WireBodyData.UnauthorizedError);

var response = await scenario.Client.Vcs.ListBranchesAsync(requestOptions: OpenCodeRequestOptions.NoThrow);

await Assert.That(response.IsError).IsTrue();
await Assert.That(response.Status).IsEqualTo(401);
await Assert.That(response.Error).IsTypeOf<UnauthorizedError>();
}

[Test]
public async Task GetBaseAsync_Should_Return_The_Typed_Base_With_Its_Location()
{
Expand Down Expand Up @@ -134,31 +84,6 @@ public async Task GetBaseAsync_Should_Return_Null_When_No_Base_Is_Inferable()
await Assert.That(response.Location.Directory).IsEqualTo(WireBodyData.ResolvedDirectory);
}

[Test]
public async Task GetBaseAsync_Should_Throw_The_Declared_503_Error()
{
using var scenario = ContractScenario.Responding(HttpStatusCode.ServiceUnavailable, WireBodyData.ServiceUnavailableError);

var exception = await Assert
.That(async () => _ = await scenario.Client.Vcs.GetBaseAsync())
.Throws<OpenCodeApiException>();

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

[Test]
public async Task GetBaseAsync_Should_Return_The_401_Error_On_The_NoThrow_Spine()
{
using var scenario = ContractScenario.Responding(HttpStatusCode.Unauthorized, WireBodyData.UnauthorizedError);

var response = await scenario.Client.Vcs.GetBaseAsync(requestOptions: OpenCodeRequestOptions.NoThrow);

await Assert.That(response.IsError).IsTrue();
await Assert.That(response.Status).IsEqualTo(401);
await Assert.That(response.Error).IsTypeOf<UnauthorizedError>();
}

[Test]
public async Task GetDiffAsync_Should_Return_The_Typed_Diffs_With_Their_Location()
{
Expand Down Expand Up @@ -193,33 +118,6 @@ await Assert.That(scenario.Requests.Single().RequestUri!.AbsoluteUri)
.IsEqualTo("http://localhost:4096/api/vcs/diff?mode=committed&base=main&context=3");
}

[Test]
public async Task GetDiffAsync_Should_Throw_The_Declared_503_Error()
{
using var scenario = ContractScenario.Responding(HttpStatusCode.ServiceUnavailable, WireBodyData.ServiceUnavailableError);

var exception = await Assert
.That(async () => _ = await scenario.Client.Vcs.GetDiffAsync(new VcsDiffRequest { Mode = VcsMode.Branch }))
.Throws<OpenCodeApiException>();

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

[Test]
public async Task GetDiffAsync_Should_Return_The_401_Error_On_The_NoThrow_Spine()
{
using var scenario = ContractScenario.Responding(HttpStatusCode.Unauthorized, WireBodyData.UnauthorizedError);

var response = await scenario.Client.Vcs.GetDiffAsync(
new VcsDiffRequest { Mode = VcsMode.Working },
OpenCodeRequestOptions.NoThrow);

await Assert.That(response.IsError).IsTrue();
await Assert.That(response.Status).IsEqualTo(401);
await Assert.That(response.Error).IsTypeOf<UnauthorizedError>();
}

[Test]
public async Task InitializeRepositoryAsync_Should_Post_The_Provider_And_Location_And_Accept_No_Content()
{
Expand Down
112 changes: 112 additions & 0 deletions tests/OpenCode.Sdk.Tests/Vcs/VcsDeclaredErrors.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System.Net;
using OpenCode.Sdk.Models;
using OpenCode.Sdk.Tests.Common.Support;
using OpenCode.Sdk.Tests.Support;

namespace OpenCode.Sdk.Tests.Vcs;

public static class VcsDeclaredErrors
{
private static readonly DeclaredErrorDoor GetStatusDoor = new(
nameof(VcsClient.GetStatusAsync),
"vcs.status",
"get",
"/api/vcs/status",
static async (client, options) => await client.Vcs.GetStatusAsync(requestOptions: options));

private static readonly DeclaredErrorDoor ListBranchesDoor = new(
nameof(VcsClient.ListBranchesAsync),
"vcs.branch.list",
"get",
"/api/vcs/branch",
static async (client, options) => await client.Vcs.ListBranchesAsync(requestOptions: options));

private static readonly DeclaredErrorDoor GetBaseDoor = new(
nameof(VcsClient.GetBaseAsync),
"vcs.base",
"get",
"/api/vcs/base",
static async (client, options) => await client.Vcs.GetBaseAsync(requestOptions: options));

private static readonly DeclaredErrorDoor GetDiffBranchDoor = new(
nameof(VcsClient.GetDiffAsync),
"vcs.diff",
"get",
"/api/vcs/diff",
static async (client, options) => await client.Vcs.GetDiffAsync(new VcsDiffRequest { Mode = VcsMode.Branch }, options));

private static readonly DeclaredErrorDoor GetDiffWorkingDoor = new(
nameof(VcsClient.GetDiffAsync),
"vcs.diff",
"get",
"/api/vcs/diff",
static async (client, options) => await client.Vcs.GetDiffAsync(new VcsDiffRequest { Mode = VcsMode.Working }, options));

public static IEnumerable<Func<DeclaredErrorCase>> Rows() =>
[
static () => new(
"vcs.status:400:InvalidRequestError",
GetStatusDoor,
HttpStatusCode.BadRequest,
WireBodyData.InvalidRequestError,
"InvalidRequestError",
typeof(InvalidRequestError),
"#/paths/~1api~1vcs~1status/get/responses/400/content/application~1json/schema"),
static () => new(
"vcs.status:401:UnauthorizedError",
GetStatusDoor,
HttpStatusCode.Unauthorized,
WireBodyData.UnauthorizedError,
"UnauthorizedError",
typeof(UnauthorizedError),
"#/paths/~1api~1vcs~1status/get/responses/401/content/application~1json/schema"),
static () => new(
"vcs.branch.list:400:InvalidRequestError",
ListBranchesDoor,
HttpStatusCode.BadRequest,
WireBodyData.InvalidRequestError,
"InvalidRequestError",
typeof(InvalidRequestError),
"#/paths/~1api~1vcs~1branch/get/responses/400/content/application~1json/schema"),
static () => new(
"vcs.branch.list:401:UnauthorizedError",
ListBranchesDoor,
HttpStatusCode.Unauthorized,
WireBodyData.UnauthorizedError,
"UnauthorizedError",
typeof(UnauthorizedError),
"#/paths/~1api~1vcs~1branch/get/responses/401/content/application~1json/schema"),
static () => new(
"vcs.base:503:ServiceUnavailableError",
GetBaseDoor,
HttpStatusCode.ServiceUnavailable,
WireBodyData.ServiceUnavailableError,
"ServiceUnavailableError",
typeof(ServiceUnavailableError),
"#/paths/~1api~1vcs~1base/get/responses/503/content/application~1json/schema"),
static () => new(
"vcs.base:401:UnauthorizedError",
GetBaseDoor,
HttpStatusCode.Unauthorized,
WireBodyData.UnauthorizedError,
"UnauthorizedError",
typeof(UnauthorizedError),
"#/paths/~1api~1vcs~1base/get/responses/401/content/application~1json/schema"),
static () => new(
"vcs.diff:503:ServiceUnavailableError",
GetDiffBranchDoor,
HttpStatusCode.ServiceUnavailable,
WireBodyData.ServiceUnavailableError,
"ServiceUnavailableError",
typeof(ServiceUnavailableError),
"#/paths/~1api~1vcs~1diff/get/responses/503/content/application~1json/schema"),
static () => new(
"vcs.diff:401:UnauthorizedError",
GetDiffWorkingDoor,
HttpStatusCode.Unauthorized,
WireBodyData.UnauthorizedError,
"UnauthorizedError",
typeof(UnauthorizedError),
"#/paths/~1api~1vcs~1diff/get/responses/401/content/application~1json/schema"),
];
}
Loading
Loading