Skip to content

Commit d3e98dd

Browse files
committed
Merge branch 'master' into onefile
2 parents cd4716e + 658475a commit d3e98dd

4 files changed

Lines changed: 98 additions & 22 deletions

File tree

CLAUDE.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
TeaPie is a lightweight CLI-based API testing framework for >=.NET 8. It executes tests defined as `.http` files with optional C# scripts (`.csx`) for pre-request setup and post-response validation. Installed as a dotnet global tool (`TeaPie.Tool`).
8+
9+
## Build & Test Commands
10+
11+
```sh
12+
dotnet restore # Restore dependencies
13+
dotnet build # Build all projects
14+
dotnet build -warnaserror # Build as CI does (warnings are errors)
15+
dotnet test # Run all tests
16+
dotnet test --filter "FullyQualifiedName~ClassName.MethodName" # Run a single test
17+
```
18+
19+
Tests use **xUnit** with **FluentAssertions** and **NSubstitute** for mocking. Test project: `tests/TeaPie.Tests/`.
20+
21+
CI builds with `dotnet build --configuration Release --no-restore -warnaserror` — all warnings must be resolved before merging.
22+
23+
## Solution Structure
24+
25+
- **`src/TeaPie/`** — Core library (NuGet package `TeaPie`). Contains all framework logic.
26+
- **`src/TeaPie.DotnetTool/`** — CLI entry point (NuGet tool `TeaPie.Tool`). Thin wrapper with Spectre.Console CLI commands (`test`, `explore`, `generate`, `init`, `compile-script`, `clear-cache`).
27+
- **`tests/TeaPie.Tests/`** — Unit tests with demo fixtures in `Demo/` subdirectories.
28+
29+
## Architecture
30+
31+
### Pipeline Pattern
32+
33+
The core execution model is a **pipeline of steps** (`IPipelineStep`). `ApplicationBuilder` configures DI services and assembles the pipeline via `ApplicationStepsFactory`, which creates different step sequences for different modes (test execution, structure exploration, script compilation).
34+
35+
Steps execute sequentially against an `ApplicationContext`. The pipeline supports dynamic step insertion during execution.
36+
37+
### Key Subsystems (all under `src/TeaPie/`)
38+
39+
- **`Pipelines/`** — Pipeline infrastructure (`IPipeline`, `IPipelineStep`, `StepsCollection`)
40+
- **`StructureExploration/`** — Discovers and organizes test collections from the file system. Maps `.http` files and their associated scripts into a `CollectionStructure` tree.
41+
- **`Http/`** — Parses `.http` files, executes HTTP requests, handles auth (`Auth/`), retries (`Retrying/`), headers
42+
- **`Scripts/`** — Loads, pre-processes (resolves `#load` and `#r "nuget:..."` directives), compiles, and executes `.csx` C# scripts via Roslyn
43+
- **`TestCases/`** — Manages test case lifecycle (init/execute/finish). A test case = `.http` file + optional `-init.csx` and `-test.csx` scripts
44+
- **`Testing/`** — Test execution engine, test directives, assertions (extends xUnit Assert), result tracking
45+
- **`Variables/`** — Variable system for sharing state between scripts and across requests
46+
- **`Environments/`** — Environment configuration (env.json files, environment switching)
47+
- **`Reporting/`** — Test result reporters (console, JUnit XML, custom)
48+
- **`Functions/`** — Built-in functions available to scripts
49+
50+
### Test Case Convention
51+
52+
A test case is a group of files sharing a base name:
53+
54+
- `{name}-req.http` — The HTTP request definition (required)
55+
- `{name}-init.csx` — Pre-request C# script (optional)
56+
- `{name}-test.csx` — Post-response validation C# script (optional)
57+
58+
### DI & Service Registration
59+
60+
Each subsystem has its own `Setup.cs` file with extension methods for `IServiceCollection`. The root `Setup.cs` in `src/TeaPie/` orchestrates all registrations via `AddTeaPie()`.
61+
62+
## Code Style
63+
64+
- .NET 8, C# with nullable enabled, implicit usings
65+
- Central package version management (`ManagePackageVersionsCentrally`)
66+
- Roslynator analyzers enforced; `EnforceCodeStyleInBuild` is on
67+
- `.editorconfig`: 4-space indent, CRLF line endings for C# files, UTF-8 with BOM
68+
- Uses file-scoped namespaces, expression-bodied members, and pattern matching throughout
69+
70+
## Branch Naming
71+
72+
Use `feature/`, `bugfix/`, `refactoring/`, or `docs/` prefixes. Main branch is `master`.
73+
74+
## Documentation
75+
76+
DocFX-based docs live in `docs/`. Build with `docfx "./docs/docfx.json"`, serve with `docfx serve _site`.

tests/TeaPie.Tests/Http/HttpMessagesExtensionsShould.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -311,24 +311,24 @@ private static void CompareDummies([NotNull] Dummy? result, Dummy? data)
311311
{
312312
NotNull(result);
313313
NotNull(data);
314-
Equal(result.Id, data.Id);
315-
Equal(result.Name, data.Name);
316-
Equal(result.IsRegistered, data.IsRegistered);
317-
Equal(result.Averages.Length, data.Averages.Length);
314+
Equal(data.Id, result.Id);
315+
Equal(data.Name, result.Name);
316+
Equal(data.IsRegistered, result.IsRegistered);
317+
Equal(data.Averages.Length, result.Averages.Length);
318318
for (var i = 0; i < result.Averages.Length; i++)
319319
{
320-
Equal(result.Averages[i], data.Averages[i]);
320+
Equal(data.Averages[i], result.Averages[i]);
321321
}
322322
}
323323

324324
private static void CompareDynamic(dynamic real, Dummy expected)
325325
{
326326
NotNull(real);
327327
NotNull(expected);
328-
Equal(real.Id, expected.Id);
329-
Equal(real.Name, expected.Name);
330-
Equal(real.IsRegistered, expected.IsRegistered);
331-
Equal(real.Averages.Count, expected.Averages.Length);
328+
Equal(expected.Id, real.Id);
329+
Equal(expected.Name, real.Name);
330+
Equal(expected.IsRegistered, real.IsRegistered);
331+
Equal(expected.Averages.Length, real.Averages.Count);
332332
foreach (var (Expected, Real) in expected.Averages.Zip(((List<object>)real.Averages).Select(x => ((JValue)x).Value)))
333333
{
334334
Equal(Expected, Real);

tests/TeaPie.Tests/Json/JsonElementTypeConverterShould.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public void ResolveBooleanFromJsonElementCorrectly()
2525
var deserialized = JsonSerializer.Deserialize<Dictionary<string, object>>(Json);
2626
var resolved = JsonElementTypeConverter.Convert(deserialized!["booleanProperty"]);
2727

28-
Assert.Equal(resolved, true);
28+
Assert.Equal(true, resolved);
2929
}
3030

3131
[Fact]
@@ -34,7 +34,7 @@ public void ResolveIntegerFromJsonElementCorrectly()
3434
var deserialized = JsonSerializer.Deserialize<Dictionary<string, object>>(Json);
3535
var resolved = JsonElementTypeConverter.Convert(deserialized!["integerProperty"]);
3636

37-
Assert.Equal(resolved, 42);
37+
Assert.Equal(42, resolved);
3838
}
3939

4040
[Fact]
@@ -43,7 +43,7 @@ public void ResolveLongFromJsonElementCorrectly()
4343
var deserialized = JsonSerializer.Deserialize<Dictionary<string, object>>(Json);
4444
var resolved = JsonElementTypeConverter.Convert(deserialized!["longProperty"]);
4545

46-
Assert.Equal(resolved, 9223372036854775807L);
46+
Assert.Equal(9223372036854775807L, resolved);
4747
}
4848

4949
[Fact]
@@ -52,7 +52,7 @@ public void ResolveDecimalFromJsonElementCorrectly()
5252
var deserialized = JsonSerializer.Deserialize<Dictionary<string, object>>(Json);
5353
var resolved = JsonElementTypeConverter.Convert(deserialized!["decimalProperty"]);
5454

55-
Assert.Equal(resolved, 12345.6789m);
55+
Assert.Equal(12345.6789m, resolved);
5656
}
5757

5858
[Fact]
@@ -61,7 +61,7 @@ public void ResolveStringFromJsonElementCorrectly()
6161
var deserialized = JsonSerializer.Deserialize<Dictionary<string, object>>(Json);
6262
var resolved = JsonElementTypeConverter.Convert(deserialized!["stringProperty"]);
6363

64-
Assert.Equal(resolved, "Hello, World!");
64+
Assert.Equal("Hello, World!", resolved);
6565
}
6666

6767
[Fact]
@@ -70,7 +70,7 @@ public void ResolveGuidFromJsonElementCorrectly()
7070
var deserialized = JsonSerializer.Deserialize<Dictionary<string, object>>(Json);
7171
var resolved = JsonElementTypeConverter.Convert(deserialized!["guidProperty"]);
7272

73-
Assert.Equal(resolved, Guid.Parse("d2713b57-3494-4d0a-8e3b-2e587f3e8b3e"));
73+
Assert.Equal(Guid.Parse("d2713b57-3494-4d0a-8e3b-2e587f3e8b3e"), resolved);
7474
}
7575

7676
[Fact]
@@ -79,7 +79,7 @@ public void ResolveDateTimeOffsetFromJsonElementCorrectly()
7979
var deserialized = JsonSerializer.Deserialize<Dictionary<string, object>>(Json);
8080
var resolved = JsonElementTypeConverter.Convert(deserialized!["dateTimeOffsetProperty"]);
8181

82-
Assert.Equal(resolved, DateTimeOffset.Parse("2025-01-27T12:34:56+01:00"));
82+
Assert.Equal(DateTimeOffset.Parse("2025-01-27T12:34:56+01:00"), resolved);
8383
}
8484

8585
[Fact]
@@ -88,6 +88,6 @@ public void ResolveArrayFromJsonElementCorrectly()
8888
var deserialized = JsonSerializer.Deserialize<Dictionary<string, object>>(Json);
8989
var resolved = JsonElementTypeConverter.Convert(deserialized!["arrayProperty"]);
9090

91-
Assert.Equal(resolved, new List<object> { 1, 2, 3, 4, 5 });
91+
Assert.Equal(new List<object> { 1, 2, 3, 4, 5 }, resolved);
9292
}
9393
}

tests/TeaPie.Tests/Json/JsonExtensionsShould.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ public void ConvertJsonStringToCaseInsensitiveExpandoObjectCorrectly()
2828
{
2929
dynamic json = JsonString.ToExpando();
3030

31-
Assert.Equal(json.stringKey, "stringValue");
32-
Assert.Equal(json.numberKey, 123);
31+
Assert.Equal("stringValue", json.stringKey);
32+
Assert.Equal(123, json.numberKey);
3333
Assert.True(json.BooleanKey);
34-
Assert.Equal(json.arrayKey.Count, 3);
34+
Assert.Equal(3, json.arrayKey.Count);
3535

3636
Assert.NotNull(json.ObjectKey);
37-
Assert.Equal(json.objectKey.NestedStringKey, "nestedValue");
38-
Assert.Equal(json.objectKey.nestedNumberKey, 456);
37+
Assert.Equal("nestedValue", json.objectKey.NestedStringKey);
38+
Assert.Equal(456, json.objectKey.nestedNumberKey);
3939
}
4040

4141
[Fact]

0 commit comments

Comments
 (0)