Skip to content

Commit fcb0884

Browse files
dennisdoomenCopilotDennis Doomen
authored
First MVP (#17)
* Initial plan * Initial exploration and fix syntax error in test file Co-authored-by: dennisdoomen <572734+dennisdoomen@users.noreply.github.com> * Implement MVP HTTP mocking functionality with tests Co-authored-by: dennisdoomen <572734+dennisdoomen@users.noreply.github.com> * Add MVP integration test demonstrating all features Co-authored-by: dennisdoomen <572734+dennisdoomen@users.noreply.github.com> * Address code review feedback: improve cross-platform compatibility, package references, and performance Co-authored-by: dennisdoomen <572734+dennisdoomen@users.noreply.github.com> * Rename HttpMockBuilder to HttpMock and Build() to GetClient(), remove intermediate Build step Co-authored-by: dennisdoomen <572734+dennisdoomen@users.noreply.github.com> * Remove backup files that were accidentally committed Co-authored-by: dennisdoomen <572734+dennisdoomen@users.noreply.github.com> * Update README with comprehensive documentation, usage examples, and feature descriptions Co-authored-by: dennisdoomen <572734+dennisdoomen@users.noreply.github.com> * Separate FluentAssertions dependencies into separate packages v7 and v8 Co-authored-by: dennisdoomen <572734+dennisdoomen@users.noreply.github.com> * Add ForHttp/ForHttps/ForHost/ForAnyHost, Reset method, shared assertion code, and attestation support Co-authored-by: dennisdoomen <572734+dennisdoomen@users.noreply.github.com> * SAVEPOINT --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: dennisdoomen <572734+dennisdoomen@users.noreply.github.com> Co-authored-by: Dennis Doomen <dennis.doomen@greenchoice.nl>
1 parent 25e1f6f commit fcb0884

File tree

60 files changed

+5717
-330
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+5717
-330
lines changed

.editorconfig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ dotnet_diagnostic.CA1062.severity = suggestion
229229
# Rationale: Very specific rule for localizable applications
230230
dotnet_diagnostic.CA1303.severity = suggestion
231231

232+
# Purpose: The behavior of StringBuilder.Append could vary based on the current user's locale settings.
233+
# Rationale: We don't want to force this in all cases
234+
dotnet_diagnostic.CA1305.severity = suggestion
235+
232236
# Purpose: Rename virtual/interface member so that it no longer conflicts with the reserved language keyword 'Throw'.
233237
# Rationale: We only support C#
234238
dotnet_diagnostic.CA1716.severity = none
@@ -401,4 +405,6 @@ dotnet_diagnostic.MA0069.severity = none # duplicate of CA2211
401405
dotnet_diagnostic.RCS1102.severity = none # duplicate of CA1052
402406
dotnet_diagnostic.RCS1188.severity = none # duplicate of CA1805
403407
dotnet_diagnostic.RCS1194.severity = none # duplicate of CA1032
408+
dotnet_diagnostic.RCS1075.severity = none # duplicate of CA1031
404409
dotnet_diagnostic.SA1401.severity = none # duplicate of CA1051
410+
dotnet_diagnostic.MA0048.severity = none # duplicate of SA1649

.junie/guidelines.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
### Mockly.Http — Developer Guidelines
2+
3+
#### Scope
4+
This document captures project-specific knowledge needed to build, test, and evolve Mockly.Http efficiently. It assumes familiarity with .NET, xUnit, and FluentAssertions.
5+
6+
---
7+
8+
### Build and Configuration
9+
10+
- Toolchain
11+
- .NET SDK: The repo uses `global.json`; current CI/runtime targets include .NET 8 and older TFMs. Local SDK 8.x is sufficient; older TFMs are built via multi-targeting.
12+
- C# language version is enforced via `Directory.Build.props` (`LangVersion` 11). Treat warnings as errors is enabled; analyzers run only for `net8.0` to keep builds fast.
13+
14+
- Solution layout (major parts)
15+
- `Mockly.Http` — main library (multi-targeted; consumed by tests and FA integration).
16+
- `FluentAssertions.Mockly.Http.v8` — FluentAssertions extensions for this library.
17+
- `Mockly.Http.Specs` — xUnit specification-style tests targeting `net6.0` and `net472`.
18+
- `Mockly.Http.ApiVerificationTests` — Public API approval tests using `PublicApiGenerator` + `Verify`.
19+
20+
- Fast local build
21+
- Use the Nuke build entrypoint for a consistent environment:
22+
- PowerShell: `./build.ps1` (auto-installs matching SDK if needed)
23+
- Bash: `./build.sh`
24+
- Or build directly with dotnet from the solution root:
25+
- `dotnet build Mockly.Http.sln -c Debug`
26+
27+
- CI-specific behaviors you may notice locally
28+
- API verification tests load the compiled `Mockly.Http.dll` for each target framework discovered in `Mockly.Http.csproj` and compare it against the baselined approved API files in `Mockly.Http.ApiVerificationTests/ApprovedApi`. When those tests fail, you need to run the contents of AcceptApiChanges.ps1 or AcceptApiChanges.sh to update the approved files.
29+
- Analyzer intensity is highest for `net8.0` builds; non-`net8.0` targets suppress analyzers to optimize time.
30+
31+
---
32+
33+
### Testing
34+
35+
- Test projects and frameworks
36+
- Specs project: `Mockly.Http.Specs` targets `net6.0` and `net472`.
37+
- Packages: `xunit` 2.5, `xunit.runner.visualstudio` 3.0, `FluentAssertions` 8.8, `FluentAssertions.Web.v8`, `coverlet.collector` for coverage.
38+
- API approval: `Mockly.Http.ApiVerificationTests` uses `PublicApiGenerator` and `VerifyXunit`. It reads the main library’s target frameworks dynamically.
39+
40+
- Run all tests
41+
- From the solution root:
42+
- `dotnet test -c Debug`
43+
- Using Rider/ReSharper, run the solution or individual projects as usual. Both `net6.0` and `net472` test TFMs are executed.
44+
45+
- Filter tests
46+
- By fully-qualified name:
47+
- `dotnet test --filter FullyQualifiedName=Mockly.Http.Specs.MocklyHttpSpecs+BasicUsage.Can_mock_delete_request`
48+
- By class:
49+
- `dotnet test --filter FullyQualifiedName~Mockly.Http.Specs.MocklyHttpSpecs+AdvancedMatching`
50+
- By trait (if you add `[Trait("Category","…")]`):
51+
- `dotnet test --filter TestCategory=Slow`
52+
53+
- Adding new tests
54+
- Place new spec classes under `Mockly.Http.Specs` and use xUnit attributes (`[Fact]`, `[Theory]`). Follow the nested-class structure used in `MocklyHttpSpecs` to group scenarios, e.g. `class WhenUsingAssertions`.
55+
- Prefer FluentAssertions for assertions. For HTTP-specific assertions, use the FA integration in `FluentAssertions.Mockly.Http.v8`.
56+
- If the test depends on matching HTTP requests:
57+
- Use `RequestMock` and the fluent builders (e.g., `mock.ForGet().ForPath("/api/*").RespondsWithJsonContent(...)`).
58+
- Consider `RequestCollection` to capture and assert on requests observed during the test.
59+
60+
- Running API verification tests
61+
- These tests live in `Mockly.Http.ApiVerificationTests` and ensure the public API of `Mockly.Http` doesn’t change unexpectedly.
62+
- To re-baseline intentionally changed APIs, run the test once, inspect the diff, and then update approved files in `Mockly.Http.ApiVerificationTests/ApprovedApi`. Use PR review to confirm intended changes.
63+
64+
- Coverage (optional)
65+
- `coverlet.collector` is already referenced. You can gather coverage via:
66+
- `dotnet test -c Debug /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura`
67+
68+
---
69+
70+
### Verified demo: adding and running a test
71+
72+
The following minimal test can be temporarily added to `Mockly.Http.Specs` to validate your environment and demonstrate filtering. We verified this flow locally and removed the file afterward to keep the repo clean.
73+
74+
1) Create `Mockly.Http.Specs/DemoEnvironmentSpecs.cs`:
75+
```
76+
using Xunit;
77+
78+
namespace Mockly.Http.Specs;
79+
80+
public class DemoEnvironmentSpecs
81+
{
82+
[Fact]
83+
public void Addition_works()
84+
=> Assert.Equal(2, 1 + 1);
85+
}
86+
```
87+
88+
2) Run only this test by FQN:
89+
```
90+
dotnet test --filter FullyQualifiedName=Mockly.Http.Specs.DemoEnvironmentSpecs.Addition_works
91+
```
92+
93+
3) Run the full solution tests:
94+
```
95+
dotnet test
96+
```
97+
98+
4) Delete `DemoEnvironmentSpecs.cs` once done.
99+
100+
---
101+
102+
### Development Notes & Conventions
103+
104+
- Code style and analyzers
105+
- The repo enforces warnings-as-errors and uses multiple analyzer packages for `net8.0`. Keep code clean to pass builds; fix or suppress with clear justification.
106+
- Prefer expression-bodied members for small helpers when consistent with surrounding code. Match existing naming and layout patterns.
107+
108+
- Public surface discipline
109+
- Any change to public types/members in `Mockly.Http` should be intentional and accompanied by an API approval update. Expect PR review to focus on public API impact and diagnostics quality.
110+
111+
- Testing guidance
112+
- Organize specs by nested classes representing feature areas, mirroring the patterns in `MocklyHttpSpecs.cs`.
113+
- Assertions: Use FluentAssertions 8. For HTTP responses, leverage `FluentAssertions.Web.v8` and `FluentAssertions.Mockly.Http.v8` extensions.
114+
- Unexpected requests: The default is fail-fast (`FailOnUnexpectedCalls = true`). When exploring scenarios that intentionally produce unexpected calls, set it to `false` locally in the test and assert on captured requests instead.
115+
116+
- Useful scripts
117+
- `build.ps1` / `build.sh` — standardized build via Nuke bootstrapper; also used in CI.
118+
- `AcceptApiChanges.ps1` — helper when aligning approved API after intentional changes (see script contents/usage in repo).
119+
120+
- Multi-targeting concerns
121+
- Where behavior differs across TFMs (e.g., regex or HTTP APIs), prefer tests that run on both `net6.0` and `net472` to catch divergence. Guard code with `#if` when absolutely necessary and keep such differences minimal and well-commented.
122+
123+
---
124+
125+
### Troubleshooting
126+
127+
- “API approval test fails, file not found for framework”
128+
- Ensure you built the solution before running tests so `Mockly.Http.dll` exists for each target. The approval test dynamically discovers target frameworks from `Mockly.Http.csproj` and loads the compiled DLL: build first, or just run `dotnet test` which builds for you.
129+
130+
- “Analyzer warnings break the build”
131+
- Focus on `net8.0` target first, as analyzers are active there. Fix issues or add justified suppressions scoped as narrowly as possible.
132+
133+
- “xUnit cannot discover tests for net472 locally”
134+
- Make sure you have a compatible .NET Framework targeting pack installed if running from legacy environments. Running through `dotnet test` generally handles both TFMs on modern SDKs.

.nuke/build.schema.json

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@
2626
"enum": [
2727
"ApiChecks",
2828
"CalculateNugetVersion",
29-
"CodeCoverage",
3029
"Compile",
3130
"Default",
31+
"GenerateCodeCoverageReport",
3232
"Pack",
33+
"PreparePackageReadme",
3334
"Push",
3435
"Restore",
35-
"RunTests"
36+
"RunInspectCode",
37+
"RunTests",
38+
"ScanPackages"
3639
]
3740
},
3841
"Verbosity": {
@@ -114,11 +117,20 @@
114117
"Release"
115118
]
116119
},
117-
"NuGetApiKey": {
120+
"GitHubApiKey": {
118121
"type": "string",
119-
"description": "The key to push to Nuget",
122+
"description": "The key to use for scanning packages on GitHub",
120123
"default": "Secrets must be entered via 'nuke :secrets [profile]'"
121124
},
125+
"NugetArtifactsApiKey": {
126+
"type": "string",
127+
"description": "The API key used to authenticate and authorize access to the NuGet artifacts API",
128+
"default": "Secrets must be entered via 'nuke :secrets [profile]'"
129+
},
130+
"NugetArtifactsApiUri": {
131+
"type": "string",
132+
"description": "Set the URI specifying the location of the NuGet artifacts API, which is used to publish packages generated during the build process"
133+
},
122134
"Solution": {
123135
"type": "string",
124136
"description": "Path to a solution file that is automatically loaded"

.nuke/parameters.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"$schema": "./build.schema.json",
3-
"Solution": "Mockly.Http.sln"
3+
"Solution": "Mockly.sln"
44
}

.packageguard/cache.bin

-19.2 KB
Binary file not shown.

.packageguard/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"MIT",
66
"Apache-2.0",
77
"BSD-3-Clause",
8-
"BSD-2-Clause"
8+
"BSD-2-Clause",
9+
"Microsoft .NET Library License"
910
],
1011
"Packages": [
1112
"FluentAssertions",

Build/Build.cs

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,15 @@ class Build : NukeBuild
116116
.DependsOn(Compile)
117117
.Executes(() =>
118118
{
119-
InspectCode($"Mockly.Http.sln -o={ArtifactsDirectory / "CodeIssues.sarif"} --no-build");
119+
InspectCode($"Mockly.sln -o={ArtifactsDirectory / "CodeIssues.sarif"} --no-build");
120120
});
121121

122122
Target RunTests => _ => _
123123
.DependsOn(Compile, RunInspectCode)
124124
.Executes(() =>
125125
{
126126
TestResultsDirectory.CreateOrCleanDirectory();
127-
var project = Solution.GetProject("Mockly.Http.Specs");
127+
var project = Solution.GetProject("Mockly.Specs");
128128

129129
DotNetTest(s => s
130130
// We run tests in debug mode so that Fluent Assertions can show the names of variables
@@ -147,7 +147,7 @@ class Build : NukeBuild
147147
.DependsOn(Compile)
148148
.Executes(() =>
149149
{
150-
var project = Solution.GetProject("Mockly.Http.ApiVerificationTests");
150+
var project = Solution.GetProject("Mockly.ApiVerificationTests");
151151

152152
DotNetTest(s => s
153153
.SetConfiguration(Configuration)
@@ -179,9 +179,31 @@ class Build : NukeBuild
179179
Information($"Code coverage report: \x1b]8;;file://{link.Replace('\\', '/')}\x1b\\{link}\x1b]8;;\x1b\\");
180180
});
181181

182+
Target PreparePackageReadme => _ => _
183+
.Executes(() =>
184+
{
185+
var content = (RootDirectory / "README.md").ReadAllText();
186+
var sections = content.Split(["\n## "], StringSplitOptions.RemoveEmptyEntries);
187+
188+
string[] headersToInclude =
189+
[
190+
"About",
191+
"Key Features",
192+
"Quick Start",
193+
"Additional notes",
194+
"Versioning",
195+
"Credits"
196+
];
197+
198+
var readmeContent = "## " + string.Join("\n## ", sections
199+
.Where(section => headersToInclude.Any(header => section.StartsWith(header, StringComparison.OrdinalIgnoreCase))));
200+
201+
(ArtifactsDirectory / "Readme.md").WriteAllText(readmeContent);
202+
});
182203

183204
Target Pack => _ => _
184205
.DependsOn(ScanPackages)
206+
.DependsOn(PreparePackageReadme)
185207
.DependsOn(CalculateNugetVersion)
186208
.DependsOn(ApiChecks)
187209
.DependsOn(GenerateCodeCoverageReport)
@@ -198,8 +220,31 @@ class Build : NukeBuild
198220
p.Rename(".nuspec", ExistsPolicy.FileOverwrite);
199221
});
200222

223+
// Pack main Mockly library
201224
DotNetPack(s => s
202-
.SetProject(Solution.GetProject("Mockly.Http"))
225+
.SetProject(Solution.GetProject("Mockly"))
226+
.SetOutputDirectory(ArtifactsDirectory)
227+
.SetConfiguration(Configuration)
228+
.EnableNoBuild()
229+
.EnableNoLogo()
230+
.EnableNoRestore()
231+
.EnableContinuousIntegrationBuild() // Necessary for deterministic builds
232+
.SetVersion(SemVer));
233+
234+
// Pack FluentAssertions.Mockly.v7 extension
235+
DotNetPack(s => s
236+
.SetProject(Solution.GetProject("FluentAssertions.Mockly.v7"))
237+
.SetOutputDirectory(ArtifactsDirectory)
238+
.SetConfiguration(Configuration)
239+
.EnableNoBuild()
240+
.EnableNoLogo()
241+
.EnableNoRestore()
242+
.EnableContinuousIntegrationBuild() // Necessary for deterministic builds
243+
.SetVersion(SemVer));
244+
245+
// Pack FluentAssertions.Mockly.v8 extension
246+
DotNetPack(s => s
247+
.SetProject(Solution.GetProject("FluentAssertions.Mockly.v8"))
203248
.SetOutputDirectory(ArtifactsDirectory)
204249
.SetConfiguration(Configuration)
205250
.EnableNoBuild()
@@ -227,6 +272,24 @@ class Build : NukeBuild
227272
.EnableNoSymbols()
228273
.CombineWith(packages,
229274
(v, path) => v.SetTargetPath(path)));
275+
276+
// Attest packages for provenance (requires GitHub CLI and OIDC token)
277+
if (GitHubActions != null)
278+
{
279+
foreach (var package in packages)
280+
{
281+
try
282+
{
283+
Information($"Attesting package: {package}");
284+
ProcessTasks.StartProcess("gh", $"attestation attest --predicate-type https://slsa.dev/provenance/v1 {package}")
285+
.AssertZeroExitCode();
286+
}
287+
catch (Exception ex)
288+
{
289+
Warning($"Failed to attest package {package}: {ex.Message}");
290+
}
291+
}
292+
}
230293
});
231294

232295
Target Default => _ => _

Build/_build.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<PackageReference Include="Nuke.Components" Version="9.0.4" />
1717
<PackageDownload Include="ReportGenerator" Version="[5.2.0]" />
1818
<PackageDownload Include="GitVersion.Tool" Version="[6.0.2]" />
19-
<PackageDownload Include="PackageGuard" Version="[1.6.0]" />
19+
<PackageDownload Include="PackageGuard" Version="[2.0.0]" />
2020
<PackageDownload Include="JetBrains.ReSharper.GlobalTools" Version="[2025.2.0]" />
2121
</ItemGroup>
2222

Directory.Build.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@
3535
<PrivateAssets>all</PrivateAssets>
3636
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
3737
</PackageReference>
38-
<PackageReference Include="Roslynator.Analyzers" Version="4.13.1">
38+
<PackageReference Include="Roslynator.Analyzers" Version="4.14.1">
3939
<PrivateAssets>all</PrivateAssets>
4040
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
4141
</PackageReference>
42-
<PackageReference Include="Meziantou.Analyzer" Version="2.0.203">
42+
<PackageReference Include="Meziantou.Analyzer" Version="2.0.257">
4343
<PrivateAssets>all</PrivateAssets>
4444
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
4545
</PackageReference>

0 commit comments

Comments
 (0)