From eb7b6bdcc188111a716a23531038319a6e8fd214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deniz=20=C4=B0rgin?= Date: Sun, 18 Oct 2026 01:36:32 +0300 Subject: [PATCH 1/2] test: fourteen Vcs and Worktrees declared-error doors become table rows; two enum duplicates go --- .../Common/Errors/DeclaredErrorTests.cs | 2 + .../Serialization/OpenCodeJsonContextTests.cs | 18 --- .../Vcs/VcsClientContractTests.cs | 102 ---------------- .../Vcs/VcsDeclaredErrors.cs | 112 ++++++++++++++++++ .../Worktrees/WorktreesClientContractTests.cs | 78 ------------ .../Worktrees/WorktreesDeclaredErrors.cs | 70 +++++++++++ 6 files changed, 184 insertions(+), 198 deletions(-) create mode 100644 tests/OpenCode.Sdk.Tests/Vcs/VcsDeclaredErrors.cs diff --git a/tests/OpenCode.Sdk.Tests/Common/Errors/DeclaredErrorTests.cs b/tests/OpenCode.Sdk.Tests/Common/Errors/DeclaredErrorTests.cs index ee1144bba..54f42ac9b 100644 --- a/tests/OpenCode.Sdk.Tests/Common/Errors/DeclaredErrorTests.cs +++ b/tests/OpenCode.Sdk.Tests/Common/Errors/DeclaredErrorTests.cs @@ -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; @@ -27,6 +28,7 @@ .. ExperimentalDeclaredErrors.Rows(), .. FormsDeclaredErrors.Rows(), .. FileSystemDeclaredErrors.Rows(), .. IntegrationsDeclaredErrors.Rows(), + .. VcsDeclaredErrors.Rows(), ]; [Test] diff --git a/tests/OpenCode.Sdk.Tests/Serialization/OpenCodeJsonContextTests.cs b/tests/OpenCode.Sdk.Tests/Serialization/OpenCodeJsonContextTests.cs index d179f484b..6346c93e9 100644 --- a/tests/OpenCode.Sdk.Tests/Serialization/OpenCodeJsonContextTests.cs +++ b/tests/OpenCode.Sdk.Tests/Serialization/OpenCodeJsonContextTests.cs @@ -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(json)).Throws(); - } - - [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(json); - - await Assert.That(diff.Status).IsEqualTo(FileDiffInfoStatus.Modified); - } - [Arguments("NaN", double.NaN)] [Arguments("Infinity", double.PositiveInfinity)] [Arguments("-Infinity", double.NegativeInfinity)] diff --git a/tests/OpenCode.Sdk.Tests/Vcs/VcsClientContractTests.cs b/tests/OpenCode.Sdk.Tests/Vcs/VcsClientContractTests.cs index 30b8a7a97..b91a6cb7d 100644 --- a/tests/OpenCode.Sdk.Tests/Vcs/VcsClientContractTests.cs +++ b/tests/OpenCode.Sdk.Tests/Vcs/VcsClientContractTests.cs @@ -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(); - - await Assert.That(exception!.Status).IsEqualTo(400); - await Assert.That(exception.Error).IsTypeOf(); - } - - [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(); - } - [Test] public async Task ListBranchesAsync_Should_Return_The_Typed_Branches_With_Their_Location() { @@ -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(); - - await Assert.That(exception!.Status).IsEqualTo(400); - await Assert.That(exception.Error).IsTypeOf(); - } - - [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(); - } - [Test] public async Task GetBaseAsync_Should_Return_The_Typed_Base_With_Its_Location() { @@ -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(); - - await Assert.That(exception!.Status).IsEqualTo(503); - await Assert.That(exception.Error).IsTypeOf(); - } - - [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(); - } - [Test] public async Task GetDiffAsync_Should_Return_The_Typed_Diffs_With_Their_Location() { @@ -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(); - - await Assert.That(exception!.Status).IsEqualTo(503); - await Assert.That(exception.Error).IsTypeOf(); - } - - [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(); - } - [Test] public async Task InitializeRepositoryAsync_Should_Post_The_Provider_And_Location_And_Accept_No_Content() { diff --git a/tests/OpenCode.Sdk.Tests/Vcs/VcsDeclaredErrors.cs b/tests/OpenCode.Sdk.Tests/Vcs/VcsDeclaredErrors.cs new file mode 100644 index 000000000..20cd95d59 --- /dev/null +++ b/tests/OpenCode.Sdk.Tests/Vcs/VcsDeclaredErrors.cs @@ -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> 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"), + ]; +} diff --git a/tests/OpenCode.Sdk.Tests/Worktrees/WorktreesClientContractTests.cs b/tests/OpenCode.Sdk.Tests/Worktrees/WorktreesClientContractTests.cs index f5f8754e7..514f03b7a 100644 --- a/tests/OpenCode.Sdk.Tests/Worktrees/WorktreesClientContractTests.cs +++ b/tests/OpenCode.Sdk.Tests/Worktrees/WorktreesClientContractTests.cs @@ -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_Return_The_401_Error_On_The_NoThrow_Spine() - { - using var scenario = ContractScenario.Responding(HttpStatusCode.Unauthorized, WireBodyData.UnauthorizedError); - - var response = await scenario.Client.Worktrees - .CreateWorktreeAsync(CreateRequest(), OpenCodeRequestOptions.NoThrow); - - await Assert.That(response.IsError).IsTrue(); - await Assert.That(response.Status).IsEqualTo(401); - await Assert.That(response.Error).IsTypeOf(); - } - /// /// The pinned document declares a required body on this DELETE, so the wire must carry it: /// a 204 that arrived without the body would be the server answering a different request. @@ -149,19 +136,6 @@ await Assert.That(request.RequestUri!.AbsoluteUri).IsEqualTo( await Assert.That(request.Body).IsEqualTo("{\"projectID\":\"prj_1\",\"directory\":\"/repo/feature\",\"force\":false}"); } - [Test] - public async Task RemoveWorktreeAsync_Should_Return_The_401_Error_On_The_NoThrow_Spine() - { - using var scenario = ContractScenario.Responding(HttpStatusCode.Unauthorized, WireBodyData.UnauthorizedError); - - var response = await scenario.Client.Worktrees - .RemoveWorktreeAsync(RemoveRequest(), OpenCodeRequestOptions.NoThrow); - - await Assert.That(response.IsError).IsTrue(); - await Assert.That(response.Status).IsEqualTo(401); - await Assert.That(response.Error).IsTypeOf(); - } - [Test] public async Task RefreshWorktreesAsync_Should_Post_On_The_Refresh_Route() { @@ -176,19 +150,6 @@ public async Task RefreshWorktreesAsync_Should_Post_On_The_Refresh_Route() await Assert.That(request.RequestUri).IsEqualTo(new Uri("http://localhost:4096/api/worktree/refresh")); } - [Test] - public async Task RefreshWorktreesAsync_Should_Throw_The_Declared_400_Worktree_Error() - { - using var scenario = ContractScenario.Responding(HttpStatusCode.BadRequest, WireBodyData.WorktreeError); - - var exception = await Assert - .That(async () => _ = await scenario.Client.Worktrees.RefreshWorktreesAsync(new WorktreeRefreshRequest { ProjectId = "prj_1" })) - .Throws(); - - await Assert.That(exception!.Status).IsEqualTo(400); - await Assert.That(exception.Error).IsTypeOf(); - } - [Test] public async Task RefreshWorktreesAsync_Should_Send_The_Project() { @@ -202,19 +163,6 @@ public async Task RefreshWorktreesAsync_Should_Send_The_Project() await Assert.That(scenario.Requests.Single().Body).IsEqualTo("{\"projectID\":\"prj_1\"}"); } - [Test] - public async Task RefreshWorktreesAsync_Should_Return_The_401_Error_On_The_NoThrow_Spine() - { - using var scenario = ContractScenario.Responding(HttpStatusCode.Unauthorized, WireBodyData.UnauthorizedError); - - var response = await scenario.Client.Worktrees - .RefreshWorktreesAsync(new WorktreeRefreshRequest { ProjectId = "prj_1" }, requestOptions: OpenCodeRequestOptions.NoThrow); - - await Assert.That(response.IsError).IsTrue(); - await Assert.That(response.Status).IsEqualTo(401); - await Assert.That(response.Error).IsTypeOf(); - } - [Test] public async Task ListWorktreesAsync_Should_Return_The_Typed_Worktrees() { @@ -270,32 +218,6 @@ public async Task ListWorktreesAsync_Should_Return_The_Declared_Project_Not_Foun await Assert.That(((ProjectNotFoundError)response.Error!).ProjectId).IsEqualTo("prj_9"); } - [Test] - public async Task ListWorktreesAsync_Should_Throw_The_Declared_400_Error() - { - using var scenario = ContractScenario.Responding(HttpStatusCode.BadRequest, WireBodyData.InvalidRequestError); - - var exception = await Assert - .That(async () => _ = await scenario.Client.Worktrees.ListWorktreesAsync(new WorktreeListRequest { ProjectId = "prj_1" })) - .Throws(); - - await Assert.That(exception!.Status).IsEqualTo(400); - await Assert.That(exception.Error).IsTypeOf(); - } - - [Test] - public async Task ListWorktreesAsync_Should_Return_The_401_Error_On_The_NoThrow_Spine() - { - using var scenario = ContractScenario.Responding(HttpStatusCode.Unauthorized, WireBodyData.UnauthorizedError); - - var response = await scenario.Client.Worktrees - .ListWorktreesAsync(new WorktreeListRequest { ProjectId = "prj_1" }, requestOptions: OpenCodeRequestOptions.NoThrow); - - await Assert.That(response.IsError).IsTrue(); - await Assert.That(response.Status).IsEqualTo(401); - await Assert.That(response.Error).IsTypeOf(); - } - private static WorktreeCreateRequest CreateRequest() => new() { diff --git a/tests/OpenCode.Sdk.Tests/Worktrees/WorktreesDeclaredErrors.cs b/tests/OpenCode.Sdk.Tests/Worktrees/WorktreesDeclaredErrors.cs index 69ca07da9..c0d0b31d1 100644 --- a/tests/OpenCode.Sdk.Tests/Worktrees/WorktreesDeclaredErrors.cs +++ b/tests/OpenCode.Sdk.Tests/Worktrees/WorktreesDeclaredErrors.cs @@ -15,6 +15,28 @@ public static class WorktreesDeclaredErrors static async (client, options) => await client.Worktrees.CreateWorktreeAsync( new WorktreeCreateRequest { ProjectId = "prj_1", Directory = "/repo/feature" }, options)); + private static readonly DeclaredErrorDoor RemoveWorktreeDoor = new( + nameof(WorktreesClient.RemoveWorktreeAsync), + "worktree.remove", + "delete", + "/api/worktree", + static async (client, options) => await client.Worktrees.RemoveWorktreeAsync( + new WorktreeRemoveRequest { ProjectId = "prj_1", Directory = "/repo/feature", Force = false }, options)); + + private static readonly DeclaredErrorDoor RefreshWorktreesDoor = new( + nameof(WorktreesClient.RefreshWorktreesAsync), + "worktree.refresh", + "post", + "/api/worktree/refresh", + static async (client, options) => await client.Worktrees.RefreshWorktreesAsync(new WorktreeRefreshRequest { ProjectId = "prj_1" }, options)); + + private static readonly DeclaredErrorDoor ListWorktreesDoor = new( + nameof(WorktreesClient.ListWorktreesAsync), + "worktree.list", + "get", + "/api/worktree", + static async (client, options) => await client.Worktrees.ListWorktreesAsync(new WorktreeListRequest { ProjectId = "prj_1" }, options)); + public static IEnumerable> Rows() => [ static () => new( @@ -25,5 +47,53 @@ public static IEnumerable> Rows() => "InvalidRequestError", typeof(InvalidRequestError), "#/paths/~1api~1worktree/post/responses/400/content/application~1json/schema/anyOf/1"), + static () => new( + "worktree.create:401:UnauthorizedError", + CreateWorktreeDoor, + HttpStatusCode.Unauthorized, + WireBodyData.UnauthorizedError, + "UnauthorizedError", + typeof(UnauthorizedError), + "#/paths/~1api~1worktree/post/responses/401/content/application~1json/schema"), + static () => new( + "worktree.remove:401:UnauthorizedError", + RemoveWorktreeDoor, + HttpStatusCode.Unauthorized, + WireBodyData.UnauthorizedError, + "UnauthorizedError", + typeof(UnauthorizedError), + "#/paths/~1api~1worktree/delete/responses/401/content/application~1json/schema"), + static () => new( + "worktree.refresh:400:WorktreeError", + RefreshWorktreesDoor, + HttpStatusCode.BadRequest, + WireBodyData.WorktreeError, + "WorktreeError", + typeof(WorktreeError), + "#/paths/~1api~1worktree~1refresh/post/responses/400/content/application~1json/schema/anyOf/0"), + static () => new( + "worktree.refresh:401:UnauthorizedError", + RefreshWorktreesDoor, + HttpStatusCode.Unauthorized, + WireBodyData.UnauthorizedError, + "UnauthorizedError", + typeof(UnauthorizedError), + "#/paths/~1api~1worktree~1refresh/post/responses/401/content/application~1json/schema"), + static () => new( + "worktree.list:400:InvalidRequestError", + ListWorktreesDoor, + HttpStatusCode.BadRequest, + WireBodyData.InvalidRequestError, + "InvalidRequestError", + typeof(InvalidRequestError), + "#/paths/~1api~1worktree/get/responses/400/content/application~1json/schema"), + static () => new( + "worktree.list:401:UnauthorizedError", + ListWorktreesDoor, + HttpStatusCode.Unauthorized, + WireBodyData.UnauthorizedError, + "UnauthorizedError", + typeof(UnauthorizedError), + "#/paths/~1api~1worktree/get/responses/401/content/application~1json/schema"), ]; } From b98af9aaec4e2e0189dfb6adc673603f1667ba77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deniz=20=C4=B0rgin?= Date: Sun, 18 Oct 2026 01:58:36 +0300 Subject: [PATCH 2/2] test: the clone baseline drops the 15 fingerprints the conversions removed, and the tests ceiling falls to 5.5% --- docs/engineering/quality-gates.md | 2 +- .../Clones/CloneDetectorTests.cs | 2 +- .../Support/CloneScenario.cs | 2 +- tools/OpenCode.Sdk.Tools/Clones/CloneScopes.cs | 4 ++-- tools/jscpd/tests.baseline.json | 15 --------------- 5 files changed, 5 insertions(+), 20 deletions(-) diff --git a/docs/engineering/quality-gates.md b/docs/engineering/quality-gates.md index e3acbf5b5..bbc2ba2f8 100644 --- a/docs/engineering/quality-gates.md +++ b/docs/engineering/quality-gates.md @@ -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 diff --git a/tests/OpenCode.Sdk.Tools.Tests/Clones/CloneDetectorTests.cs b/tests/OpenCode.Sdk.Tools.Tests/Clones/CloneDetectorTests.cs index 177ccf505..8b20b91f3 100644 --- a/tests/OpenCode.Sdk.Tools.Tests/Clones/CloneDetectorTests.cs +++ b/tests/OpenCode.Sdk.Tools.Tests/Clones/CloneDetectorTests.cs @@ -97,7 +97,7 @@ public async Task Arguments_Should_Gate_Tests_Against_Its_Baseline_And_The_Thres var arguments = CloneDetector.Arguments(Scope("tests"), Config, updateBaseline: false); await Assert.That(arguments).IsEquivalentTo( - [CloneDetector.RunnerPath, "tests", "--config", Config, FailOnEmpty, "--baseline", "tools/jscpd/tests.baseline.json", Fingerprints, "--threshold", "6"], CollectionOrdering.Matching); + [CloneDetector.RunnerPath, "tests", "--config", Config, FailOnEmpty, "--baseline", "tools/jscpd/tests.baseline.json", Fingerprints, "--threshold", "5.5"], CollectionOrdering.Matching); } [Test] diff --git a/tests/OpenCode.Sdk.Tools.Tests/Support/CloneScenario.cs b/tests/OpenCode.Sdk.Tools.Tests/Support/CloneScenario.cs index 3d6bfafb3..255e254d7 100644 --- a/tests/OpenCode.Sdk.Tools.Tests/Support/CloneScenario.cs +++ b/tests/OpenCode.Sdk.Tools.Tests/Support/CloneScenario.cs @@ -227,7 +227,7 @@ public CloneScenario WithRun(string scope, int exitCode = 0, int clones = 0, int tokens.AddRange(!_baselines || updateBaseline ? ["--update-baseline"] : scope switch { "tools" => ["--fail-on-new-clones", "--threshold", "3"], - "tests" => ["--fail-on-new-clones", "--threshold", "6"], + "tests" => ["--fail-on-new-clones", "--threshold", "5.5"], _ => ["--fail-on-new-clones"], }); } diff --git a/tools/OpenCode.Sdk.Tools/Clones/CloneScopes.cs b/tools/OpenCode.Sdk.Tools/Clones/CloneScopes.cs index b441b3273..64f958cf1 100644 --- a/tools/OpenCode.Sdk.Tools/Clones/CloneScopes.cs +++ b/tools/OpenCode.Sdk.Tools/Clones/CloneScopes.cs @@ -2,7 +2,7 @@ namespace OpenCode.Sdk.Tools.Clones; /// /// The clone gate's fixed scopes. Hand-written SDK code, the tool and the tests are gated against -/// committed baselines, the tool also against a 3% ceiling and the tests against a 6% ceiling that +/// committed baselines, the tool also against a 3% ceiling and the tests against a 5.5% ceiling that /// only falls; generated output is reported only, because an emitter's template repeats on purpose. /// internal static class CloneScopes @@ -15,7 +15,7 @@ internal static class CloneScopes [ new("src", "src", CloneScopeFilter.Generated, [], DetectorFolder + "/src.baseline.json", Threshold: null), new("tools", "tools", CloneScopeFilter.None, [DetectorFolder + "/**"], DetectorFolder + "/tools.baseline.json", Threshold: 3m), - new("tests", "tests", CloneScopeFilter.None, [], DetectorFolder + "/tests.baseline.json", Threshold: 6m), + new("tests", "tests", CloneScopeFilter.None, [], DetectorFolder + "/tests.baseline.json", Threshold: 5.5m), new("generated", "src", CloneScopeFilter.HandWritten, [], Baseline: null, Threshold: null), ]; } diff --git a/tools/jscpd/tests.baseline.json b/tools/jscpd/tests.baseline.json index 592d1c164..e8777a645 100644 --- a/tools/jscpd/tests.baseline.json +++ b/tools/jscpd/tests.baseline.json @@ -93,7 +93,6 @@ "24c472b27799aec8": 1, "24cc85d24f0b3dfa": 1, "2550fac4318fc198": 1, - "255c30af16e9acf0": 1, "255c9c6d8752d5b1": 1, "25868f84187d3eb9": 1, "26bcf1eae13cefb9": 1, @@ -111,7 +110,6 @@ "2cd64c5a0bd7b129": 1, "2d01f1bea1f9ea7e": 1, "2d0c7764aa30effc": 1, - "2d84f5d49f525727": 1, "2e6446a9c3e54be2": 1, "2ebc929ec99531d7": 1, "2ef093de84b22985": 1, @@ -126,7 +124,6 @@ "31079b601cabad85": 1, "325aedadc9c51610": 1, "32813351a4512ab1": 1, - "328f8d27e9d17017": 1, "32a53e20ee7c711b": 1, "34812987b8a4a722": 1, "350995582d58fa70": 1, @@ -166,7 +163,6 @@ "4372939ffb05b447": 1, "442313bd038e5928": 1, "44259b731f378b4b": 1, - "4489253ca84315db": 1, "46838631f0c798bd": 1, "479fdc49d7da2d6e": 1, "47c6dec036a94d60": 1, @@ -206,7 +202,6 @@ "561f3f3bf4df34b5": 1, "56446320884d2a6b": 1, "567e66ef84568376": 2, - "5684435994da0e61": 1, "573c75d83c9a3c95": 1, "57470feca6bbb65f": 1, "57f025d61c1ae125": 1, @@ -310,7 +305,6 @@ "7eda9e729d5869ca": 1, "7ee9b7831ad8eeb9": 1, "7f8b8b1b7f8ac7c9": 1, - "7fbd532cf3994d55": 1, "7ffcd9e9160f6b45": 1, "80cc7036ff095cb9": 1, "813356530d4e26c5": 1, @@ -326,7 +320,6 @@ "852958b905b8447d": 1, "852e00262bbefd12": 1, "859240ba1081004a": 1, - "863bcdab05f4bbc0": 1, "86629d252be8ae75": 1, "868313d60a5b083d": 1, "86c12e9faa8ad5f5": 1, @@ -363,7 +356,6 @@ "93c7ffbef646d5bd": 1, "942a5e56acc61557": 1, "94c3b038871234a4": 1, - "950d14b87d7527d0": 1, "9676160daa1042e5": 1, "96af7cbd3e81daa0": 1, "9720751d6958956a": 1, @@ -402,7 +394,6 @@ "a7ce419852c3f4bb": 1, "a888b3cdad3d452d": 1, "a90556221babeb5a": 1, - "a97d033336de820c": 1, "aa6af3c307cb3b1b": 1, "aa8c5d1539d3f39d": 1, "aac0860cac7db3e7": 1, @@ -423,7 +414,6 @@ "b11e207acaec82c8": 1, "b17bbc10f934e336": 1, "b26ec09aa9c7d788": 1, - "b402646e76b940bc": 1, "b4f1d187378630d2": 1, "b516fc87f57ff552": 1, "b56ef55aaf8bf7ba": 1, @@ -465,7 +455,6 @@ "c59efd159d199356": 1, "c661cc78e1086df2": 1, "c6cce4bd6bf12d59": 1, - "c94857b72d464212": 1, "c99b81e4ed3d76fb": 1, "c99b9aa8b9c89f27": 1, "c9bbce25ca3ab0c1": 1, @@ -505,7 +494,6 @@ "de25b177ad221b80": 1, "df712485fc9d33c5": 1, "e17ab17b76a1e87f": 1, - "e1e2fd8c426ea2a9": 1, "e22e40dfd5665fc6": 1, "e241bd99b28fc221": 1, "e2ee78e03bfc94f9": 1, @@ -523,12 +511,10 @@ "e841f14c1576be4d": 1, "e880470757e4f716": 1, "e9a4f95d6cc5b939": 1, - "ea178bb1fa8ad8a6": 1, "eaee9a41a0b130d0": 1, "eb0bbed02f67f613": 1, "ebec59933509b551": 1, "ebfeb862351f9538": 1, - "ec0ee1421b89f375": 1, "ecefd42b73bc5e3c": 1, "ed10c82591b10abb": 1, "ed54a54bce71c168": 1, @@ -546,7 +532,6 @@ "f176cdac923927ee": 1, "f1812b941eb02542": 1, "f1f7493a31b45480": 1, - "f23585e99b5a0ecb": 1, "f23a5dc8edef52e2": 1, "f279b2bb369cd7f1": 1, "f29076103ef900ac": 1,