Skip to content

Commit 19cf21b

Browse files
Burgynclaude
andcommitted
Address PR #84 review: change .tp delimiter from ### to ---, refactor and add --single-file generate
- Replace ### section markers with --- to avoid collision with HTTP request separator - Move .tp constants to dedicated TpConstants.cs (following AuthConstants.cs pattern) - Use Constants.UnixEndOfLine/WindowsEndOfLine in TpFileParser - Extract duplicate if blocks into SetVirtualScript() in InitializeTestCaseStep - Add --single-file/-s flag to generate command for .tp scaffolding - Allow omitting name on single --- TESTCASE marker (fallback to filename) - Update Path descriptions in Explore/Test commands to mention .tp files - Fix double whitespace in CompileScriptCommand - Add descriptive comments to 004-Car-Operations.tp demo file - Update all documentation and skill references - Remove unnecessary comments from StructureExplorationIndex - Add explanatory comments on early-return in ReadHttpFileStep/ReadScriptFileStep Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d3e98dd commit 19cf21b

22 files changed

Lines changed: 263 additions & 178 deletions

File tree

.cursor/skills/teapie/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ description: Comprehensive TeaPie framework expertise for API integration testin
77

88
## Overview
99

10-
TeaPie (TEsting API Extension) is a lightweight CLI tool for API testing that combines `.http` or `.tp` files with C# scripts for comprehensive integration testing. This skill provides expert knowledge on all TeaPie capabilities, syntax, and best practices.
10+
TeaPie (TEsting API Extension) is a lightweight CLI tool for API testing that combines `.http` files with C# scripts or uses `.tp` files that cover both types of files within one for comprehensive integration testing. This skill provides expert knowledge on all TeaPie capabilities, syntax, and best practices.
1111

1212
## Quick Reference
1313

@@ -23,7 +23,7 @@ TeaPie supports two equivalent formats. Choose based on script complexity and st
2323

2424
**Single-file format (`.tp`):**
2525

26-
- **`<name>.tp`** - All sections in one file using markers: `### TESTCASE`, `### INIT`, `### HTTP`, `### TEST`, `### END`
26+
- **`<name>.tp`** - All sections in one file using markers: `--- TESTCASE`, `--- INIT`, `--- HTTP`, `--- TEST`, `--- END`
2727

2828
### Naming Convention
2929

.cursor/skills/teapie/references/test-structure.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ TeaPie supports two equivalent formats. Choose based on script complexity and st
1919

2020
### Single-File Format (`.tp`)
2121

22-
- **`<name>.tp`** - All sections in one file using markers: `### TESTCASE`, `### INIT`, `### HTTP`, `### TEST`, `### END`
22+
- **`<name>.tp`** - All sections in one file using markers: `--- TESTCASE`, `--- INIT`, `--- HTTP`, `--- TEST`, `--- END`
2323
- Optional alternative to multi-file format; supports single or multiple test cases per file
2424

2525
### File Naming Convention
@@ -99,9 +99,9 @@ Test cases are executed in alphabetical order (ensured by numeric prefixes):
9999
1. Structure exploration - Scans for test cases (`-req.http` files and `.tp` files) and related files
100100
2. Initialization script execution (if present)
101101
3. For each test case (in alphabetical order):
102-
- Pre-request script execution (`-init.csx` or `### INIT` section in `.tp`)
103-
- HTTP request(s) execution (`-req.http` or `### HTTP` section in `.tp`)
104-
- Post-response script execution (`-test.csx` or `### TEST` section in `.tp`)
102+
- Pre-request script execution (`-init.csx` or `--- INIT` section in `.tp`)
103+
- HTTP request(s) execution (`-req.http` or `--- HTTP` section in `.tp`)
104+
- Post-response script execution (`-test.csx` or `--- TEST` section in `.tp`)
105105

106106
## .teapie Folder Structure
107107

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
1-
### TESTCASE Add Another Car
2-
3-
### INIT
1+
// This file uses the single-file (.tp) format. A .tp file can contain one or more test cases,
2+
// each defined by section markers: --- TESTCASE, --- INIT, --- HTTP, --- TEST, and --- END.
3+
//
4+
// --- TESTCASE <Name> Starts a named test case (required when multiple test cases are in one file).
5+
// --- INIT Pre-request C# script (optional). Same role as <name>-init.csx.
6+
// --- HTTP HTTP request definition (required). Same format as .http files.
7+
// --- TEST Post-response validation script (optional). Same role as <name>-test.csx.
8+
// --- END Ends the current test case block.
9+
//
10+
// Both .tp and multi-file formats can co-exist in the same collection.
11+
12+
--- TESTCASE Add Another Car
13+
14+
--- INIT
415
// Load the shared car generator definition.
516
#load "$teapie/Definitions/GenerateNewCar.csx"
617

718
var car = GenerateCar();
819
tp.SetVariable("AnotherCar", car.ToJsonString(), "cars");
920

10-
### HTTP
21+
--- HTTP
1122
## TEST-EXPECT-STATUS: [201]
1223
## TEST-HAS-BODY
1324
// Add a new car using data prepared in the INIT section.
@@ -17,7 +28,7 @@ Content-Type: application/json
1728

1829
{{AnotherCar}}
1930

20-
### TEST
31+
--- TEST
2132
await tp.Test("Newly added car should be returned with an assigned Id.", async () =>
2233
{
2334
dynamic responseJson = await tp.Responses["AddAnotherCarRequest"].GetBodyAsExpandoAsync();
@@ -28,22 +39,22 @@ await tp.Test("Newly added car should be returned with an assigned Id.", async (
2839
tp.SetVariable("AnotherCarId", (long)responseJson.Id, "cars");
2940
});
3041

31-
### END
42+
--- END
3243

33-
### TESTCASE Get All Cars
44+
--- TESTCASE Get All Cars
3445

35-
### HTTP
46+
--- HTTP
3647
## TEST-EXPECT-STATUS: [200]
3748
## TEST-HAS-BODY
3849
// Retrieve the full list of cars.
3950
# @name GetAllCarsRequest
4051
GET {{ApiBaseUrl}}{{ApiCarsSection}}
4152

42-
### TEST
53+
--- TEST
4354
tp.Test("Response body should not be empty.", () =>
4455
{
4556
NotNull(tp.Response);
4657
NotNull(tp.Response.Content);
4758
});
4859

49-
### END
60+
--- END
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
### TESTCASE Health Check
1+
--- TESTCASE Health Check
22

3-
### HTTP
3+
--- HTTP
44
## TEST-EXPECT-STATUS: [200]
55
## TEST-HAS-BODY
66
# This test case uses the .tp format - a single file combining HTTP request and test script.
77
GET {{ApiBaseUrl}}/health
88

9-
### TEST
9+
--- TEST
1010
tp.Test("Health response should contain status field.", async () =>
1111
{
1212
dynamic responseJson = await tp.Response.GetBodyAsExpandoAsync();
1313
NotNull(responseJson);
1414
NotNull(responseJson.status);
1515
});
1616

17-
### END
17+
--- END

docs/docs/introduction.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ To get started, visit **[Getting Started](getting-started.md)**.
88

99
**Universal HTTP Request Definition** – Define API tests using `.http` files.
1010

11-
**Flexible Test Structure** – Choose multi-file or single-file (`.tp`) format depending on script complexity and structure preferences.
11+
**Flexible Test Structure** – Choose multi-file or single-file (`.tp`) format depending on test case complexity and structure preferences.
1212

1313
**Pre-Request & Post-Response Scripts** – Extend test cases with C# scripts for data setup and validation.
1414

docs/docs/test-case/test-case.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ When running a test case, you can also reference:
3131

3232
TeaPie supports two equivalent ways to define test cases. Use whichever fits your project better.
3333

34-
The [**Single-File Format (`.tp`)**](tp-file.md) combines HTTP requests and C# scripts in one file using section markers. It is an optional alternative to the multi-file format. Both are fully supported; choose based on script complexity and how you prefer to structure tests.
34+
The [**Single-File Format (`.tp`)**](tp-file.md) combines HTTP requests and C# scripts in one file using section markers. It is an optional alternative to the multi-file format. Both formats are fully supported and can co-exist in the same project; choose based on test case complexity and how you prefer to structure tests.
3535

3636
## Running a Test Case
3737

@@ -84,7 +84,13 @@ This command generates the multi-file format in the specified path (or the curre
8484

8585
To **disable pre-request or post-response script generation**, set the `-i` and `-t` options to `false`.
8686

87-
For the single-file format (`.tp`), create the file manually. See [Single-File Format (`.tp`)](tp-file.md) for details.
87+
To generate a **single-file test case (`.tp`)** instead, use the `--single-file` (or `-s`) flag:
88+
89+
```sh
90+
teapie generate <test-case-name> [path] -s [-i] [-t]
91+
```
92+
93+
This creates a `<test-case-name>.tp` file with `--- HTTP` section (and `--- INIT`/`--- TEST` if `-i`/`-t` are set). See [Single-File Format (`.tp`)](tp-file.md) for details.
8894

8995
## Exploring Test Case Structure
9096

docs/docs/test-case/tp-file.md

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,62 +5,62 @@
55
|----------------------|----------------|
66
| **Definition** | An optional alternative to the multi-file format. A `.tp` file combines HTTP requests and C# scripts in one file using section markers. |
77
| **Naming Convention** | `<test-case-name>.tp` |
8-
| **Purpose** | Define test cases with all sections in one place. Both `.tp` and multi-file formats are fully supported; choose based on script complexity and how you prefer to structure tests. |
8+
| **Purpose** | Define test cases with all sections in one place. Both `.tp` and multi-file formats are fully supported and can co-exist in the same project; choose based on test case complexity and how you prefer to structure tests. |
99
| **Example Usage** | [Health Check](https://github.com/Kros-sk/TeaPie/blob/master/demo/Tests/004-Health/001-Health-Check.tp), [Car Operations](https://github.com/Kros-sk/TeaPie/blob/master/demo/Tests/002-Cars/004-Car-Operations.tp) |
1010
<!-- markdownlint-enable MD060 -->
1111

12-
The `.tp` format is an **optional alternative** to the multi-file format. Both are fully supported; choose based on script complexity and how you prefer to structure tests.
12+
The `.tp` format is an **optional alternative** to the multi-file format. Both are fully supported and can co-exist in the same project; choose based on test case complexity and how you prefer to structure tests.
1313

1414
## Section Markers
1515

16-
Sections are marked with `###` markers (case-insensitive):
16+
Sections are marked with `---` markers (case-insensitive):
1717

1818
<!-- markdownlint-disable MD060 -->
1919
| Marker | Purpose |
2020
|--------|---------|
21-
| `### TESTCASE <Name>` | Starts a named test case. Required when defining multiple test cases in one file. |
22-
| `### INIT` | Pre-request C# script (optional). Same role as `-init.csx`. |
23-
| `### HTTP` | HTTP request(s) (required). Same format as `.http` files. |
24-
| `### TEST` | Post-response C# script (optional). Same role as `-test.csx`. |
25-
| `### END` | Ends the current test case block. |
21+
| `--- TESTCASE <Name>` | Starts a named test case. Required when defining multiple test cases in one file. |
22+
| `--- INIT` | Pre-request C# script (optional). Same role as `-init.csx`. |
23+
| `--- HTTP` | HTTP request(s) (required). Same format as `.http` files. |
24+
| `--- TEST` | Post-response C# script (optional). Same role as `-test.csx`. |
25+
| `--- END` | Ends the current test case block. |
2626
<!-- markdownlint-enable MD060 -->
2727

2828
## Single Test Case Example
2929

3030
Use `## TEST-EXPECT-STATUS` and other directives in the HTTP section for status code validation. In the TEST section, focus on response body and business logic validation:
3131

3232
```text
33-
### TESTCASE Health Check
33+
--- TESTCASE Health Check
3434
35-
### HTTP
35+
--- HTTP
3636
## TEST-EXPECT-STATUS: [200]
3737
## TEST-HAS-BODY
3838
GET {{ApiBaseUrl}}/health
3939
40-
### TEST
40+
--- TEST
4141
tp.Test("Health response should contain status field.", async () =>
4242
{
4343
dynamic responseJson = await tp.Response.GetBodyAsExpandoAsync();
4444
NotNull(responseJson);
4545
NotNull(responseJson.status);
4646
});
4747
48-
### END
48+
--- END
4949
```
5050

5151
## Multiple Test Cases in One File
5252

5353
A single `.tp` file can contain multiple test cases, each with its own sections:
5454

5555
```text
56-
### TESTCASE Add Another Car
56+
--- TESTCASE Add Another Car
5757
58-
### INIT
58+
--- INIT
5959
#load "$teapie/Definitions/GenerateNewCar.csx"
6060
var car = GenerateCar();
6161
tp.SetVariable("AnotherCar", car.ToJsonString(), "cars");
6262
63-
### HTTP
63+
--- HTTP
6464
## TEST-EXPECT-STATUS: [201]
6565
## TEST-HAS-BODY
6666
# @name AddAnotherCarRequest
@@ -69,7 +69,7 @@ Content-Type: application/json
6969
7070
{{AnotherCar}}
7171
72-
### TEST
72+
--- TEST
7373
await tp.Test("Newly added car should be returned with an assigned Id.", async () =>
7474
{
7575
dynamic responseJson = await tp.Responses["AddAnotherCarRequest"].GetBodyAsExpandoAsync();
@@ -78,38 +78,38 @@ await tp.Test("Newly added car should be returned with an assigned Id.", async (
7878
tp.SetVariable("AnotherCarId", (long)responseJson.Id, "cars");
7979
});
8080
81-
### END
81+
--- END
8282
83-
### TESTCASE Get All Cars
83+
--- TESTCASE Get All Cars
8484
85-
### HTTP
85+
--- HTTP
8686
## TEST-EXPECT-STATUS: [200]
8787
## TEST-HAS-BODY
8888
# @name GetAllCarsRequest
8989
GET {{ApiBaseUrl}}{{ApiCarsSection}}
9090
91-
### TEST
91+
--- TEST
9292
await tp.Test("Response should contain array of cars.", async () =>
9393
{
9494
var body = await tp.Response.Content.ReadAsStringAsync();
9595
NotNull(body);
9696
True(body.StartsWith("[") && body.EndsWith("]"));
9797
});
9898
99-
### END
99+
--- END
100100
```
101101

102102
## Implicit Mode
103103

104-
If no `### TESTCASE` marker is present, the file is treated as a single test case. The test case name is derived from the filename (e.g. `001-Health-Check.tp``001-Health-Check`).
104+
If no `--- TESTCASE` marker is present, the file is treated as a single test case. The test case name is derived from the filename (e.g. `001-Health-Check.tp``001-Health-Check`).
105105

106106
## Rules
107107

108-
- The `### HTTP` section is **required** for each test case.
109-
- `### INIT` and `### TEST` are optional.
110-
- Markers are **case-insensitive** (`### testcase`, `### http`, etc.).
111-
- Use `### END` to terminate each test case when defining multiple test cases.
112-
- HTTP content follows the same conventions as [Request File](request-file.md) (named requests, variables, etc.).
108+
- The `--- HTTP` section is **required** for each test case.
109+
- `--- INIT` and `--- TEST` are optional.
110+
- Markers are **case-insensitive** (`--- testcase`, `--- http`, etc.).
111+
- Use `--- END` to terminate each test case when defining multiple test cases.
112+
- HTTP content follows the same conventions as [Request File](request-file.md) (named requests, variables, etc.). The `###` request separator used in `.http` files works normally inside the `--- HTTP` section without any conflict.
113113
- For status code validation, use directives such as `## TEST-EXPECT-STATUS: [200, 201]` in the HTTP section rather than asserting in the TEST script. See [Directives](directives.md) for details.
114114

115115
## When to Use `.tp` vs Multi-File

src/TeaPie.DotnetTool/CompileScriptCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ private async Task<int> CompileTpScripts(string path, Settings settings)
7676

7777
if (string.IsNullOrWhiteSpace(def.InitContent) && string.IsNullOrWhiteSpace(def.TestContent))
7878
{
79-
AnsiConsole.MarkupLine("[grey] No script sections to compile (HTTP only).[/]");
79+
AnsiConsole.MarkupLine("[grey] No script sections to compile (HTTP only).[/]");
8080
}
8181
}
8282

src/TeaPie.DotnetTool/ExploreCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ protected override ApplicationBuilder ConfigureApplication(Settings settings)
2828
public sealed class Settings : LoggingSettings
2929
{
3030
[CommandArgument(0, "[path]")]
31-
[Description("Path to collection or test case which will be explored. Defaults to the current directory.")]
31+
[Description("Path to collection, test case (.http) or single-file test case (.tp) which will be explored. Defaults to the current directory.")]
3232
public string? Path { get; init; }
3333

3434
[CommandOption("--env-file|--environment-file")]

src/TeaPie.DotnetTool/GenerateCommand.cs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using Spectre.Console;
22
using Spectre.Console.Cli;
33
using System.ComponentModel;
4+
using System.Text;
45
using TeaPie.StructureExploration.Paths;
6+
using TeaPie.TestCases;
57

68
namespace TeaPie.DotnetTool;
79

@@ -11,7 +13,14 @@ public override int Execute(CommandContext context, Settings settings)
1113
{
1214
var path = ResolvePathAndCreateDirectoryIfNeeded(settings);
1315

14-
GenerateFiles(settings, path);
16+
if (settings.IsSingleFile)
17+
{
18+
GenerateSingleFile(settings, path);
19+
}
20+
else
21+
{
22+
GenerateFiles(settings, path);
23+
}
1524

1625
ReportSuccessfullCreation(settings, path);
1726

@@ -74,6 +83,37 @@ private static string GetPreRequestFileName(string name)
7483
private static string GetPostResponseFileName(string name)
7584
=> name + Constants.PostResponseSuffix + Constants.ScriptFileExtension;
7685

86+
private static void GenerateSingleFile(Settings settings, string path)
87+
{
88+
var filePath = Path.Combine(path, settings.Name + Constants.TestCaseFileExtension);
89+
var sb = new StringBuilder();
90+
91+
sb.AppendLine($"{TpConstants.TestCaseMarker} {settings.Name}");
92+
sb.AppendLine();
93+
94+
if (settings.HasPreRequestScript)
95+
{
96+
sb.AppendLine(TpConstants.InitMarker);
97+
sb.AppendLine();
98+
sb.AppendLine();
99+
}
100+
101+
sb.AppendLine(TpConstants.HttpMarker);
102+
sb.AppendLine();
103+
sb.AppendLine();
104+
105+
if (settings.HasPostResponseScript)
106+
{
107+
sb.AppendLine(TpConstants.TestMarker);
108+
sb.AppendLine();
109+
sb.AppendLine();
110+
}
111+
112+
sb.AppendLine(TpConstants.EndMarker);
113+
114+
System.IO.File.WriteAllText(filePath, sb.ToString());
115+
}
116+
77117
private static void ReportSuccessfullCreation(Settings settings, string path)
78118
{
79119
const string beginningOfSentence = "[green]Test case [/]";
@@ -115,5 +155,10 @@ public sealed class Settings : CommandSettings
115155
[DefaultValue(false)]
116156
[Description("Indicates whether to generate post-response script (with '-test' suffix).")]
117157
public bool HasPostResponseScript { get; init; }
158+
159+
[CommandOption("-s|--single-file")]
160+
[DefaultValue(false)]
161+
[Description("Generate a single-file test case (.tp) instead of multi-file format.")]
162+
public bool IsSingleFile { get; init; }
118163
}
119164
}

0 commit comments

Comments
 (0)