Skip to content

Commit e852f43

Browse files
authored
Drop response ID field and restructure TestCase type (#12)
Remove ID field from responses while keeping it required in requests. Response IDs are unnecessary since test cases are executed sequentially against handlers. Restructure TestCase to use explicit Request and Response fields instead of flat structure, making validation against future JSON schemas easier.
1 parent e340847 commit e852f43

File tree

7 files changed

+205
-242
lines changed

7 files changed

+205
-242
lines changed

cmd/mock-handler/main.go

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,17 @@ func buildTestIndex() (map[string]string, error) {
5353
// Parse just enough to get test IDs
5454
var suite struct {
5555
Tests []struct {
56-
ID string `json:"id"`
56+
Request struct {
57+
ID string `json:"id"`
58+
} `json:"request"`
5759
} `json:"tests"`
5860
}
5961
if err := json.Unmarshal(data, &suite); err != nil {
6062
return nil, fmt.Errorf("failed to parse test file %s: %w", testFile, err)
6163
}
6264

6365
for _, test := range suite.Tests {
64-
index[test.ID] = testFile
66+
index[test.Request.ID] = testFile
6567
}
6668
}
6769

@@ -79,7 +81,6 @@ func handleRequest(line string, testIndex map[string]string) error {
7981
filename, ok := testIndex[req.ID]
8082
if !ok {
8183
resp := runner.Response{
82-
ID: req.ID,
8384
Error: &runner.Error{
8485
Code: &runner.ErrorCode{
8586
Type: "Handler",
@@ -94,7 +95,6 @@ func handleRequest(line string, testIndex map[string]string) error {
9495
suite, err := runner.LoadTestSuiteFromFS(testdata.FS, filename)
9596
if err != nil {
9697
resp := runner.Response{
97-
ID: req.ID,
9898
Error: &runner.Error{
9999
Code: &runner.ErrorCode{
100100
Type: "Handler",
@@ -108,14 +108,13 @@ func handleRequest(line string, testIndex map[string]string) error {
108108
// Find the specific test case
109109
var testCase *runner.TestCase
110110
for _, test := range suite.Tests {
111-
if test.ID == req.ID {
111+
if test.Request.ID == req.ID {
112112
testCase = &test
113113
break
114114
}
115115
}
116116
if testCase == nil {
117117
resp := runner.Response{
118-
ID: req.ID,
119118
Error: &runner.Error{
120119
Code: &runner.ErrorCode{
121120
Type: "Handler",
@@ -127,9 +126,8 @@ func handleRequest(line string, testIndex map[string]string) error {
127126
}
128127

129128
// Verify method matches
130-
if req.Method != testCase.Method {
129+
if req.Method != testCase.Request.Method {
131130
resp := runner.Response{
132-
ID: req.ID,
133131
Error: &runner.Error{
134132
Code: &runner.ErrorCode{
135133
Type: "Handler",
@@ -142,9 +140,8 @@ func handleRequest(line string, testIndex map[string]string) error {
142140

143141
// Build response based on expected result
144142
return writeResponse(runner.Response{
145-
ID: req.ID,
146-
Result: testCase.Expected.Result,
147-
Error: testCase.Expected.Error,
143+
Result: testCase.ExpectedResponse.Result,
144+
Error: testCase.ExpectedResponse.Error,
148145
})
149146
}
150147

docs/handler-spec.md

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ Handlers communicate with the test runner via **stdin/stdout**:
3030

3131
```json
3232
{
33-
"id": "unique-request-id",
3433
"result": null,
3534
"error": {
3635
"code": {
@@ -42,7 +41,6 @@ Handlers communicate with the test runner via **stdin/stdout**:
4241
```
4342

4443
**Fields:**
45-
- `id` (string, required): Must match the request ID
4644
- `result` (any, optional): The return value, or `null` for void/nullptr operations. Must be `null` on error
4745
- `error` (object, optional): Error details. Must be `null` on success. An empty object `{}` is used to indicate an error is raised without further details, it is NOT equivalent to `null`
4846
- `code` (object, optional): Error code details
@@ -55,9 +53,8 @@ Handlers communicate with the test runner via **stdin/stdout**:
5553

5654
1. **Input Processing**: Read JSON requests line-by-line from stdin
5755
2. **Response Order**: Responses must match request order (process sequentially)
58-
3. **ID Matching**: Response `id` must exactly match the request `id`
59-
4. **Error Handling**: Return error responses for invalid requests or failed operations
60-
5. **Exit Behavior**: Exit cleanly when stdin closes
56+
3. **Error Handling**: Return error responses for invalid requests or failed operations
57+
4. **Exit Behavior**: Exit cleanly when stdin closes
6158

6259
## Test Suites and Expected Responses
6360

@@ -73,14 +70,12 @@ Test cases where the script verification operation executes successfully and ret
7370
**Expected Response Format:**
7471
```json
7572
{
76-
"id": "test-id",
7773
"result": true
7874
}
7975
```
8076
or
8177
```json
8278
{
83-
"id": "test-id",
8479
"result": false
8580
}
8681
```
@@ -95,7 +90,6 @@ Test cases where the verification operation fails to determine validity of the s
9590
**Expected Response Format:**
9691
```json
9792
{
98-
"id": "test-id",
9993
"result": null,
10094
"error": {
10195
"code": {

runner/runner.go

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -126,23 +126,17 @@ func (tr *TestRunner) runTest(ctx context.Context, test TestCase) SingleTestResu
126126
select {
127127
case <-ctx.Done():
128128
return SingleTestResult{
129-
TestID: test.ID,
129+
TestID: test.Request.ID,
130130
Passed: false,
131131
Message: fmt.Sprintf("Total execution timeout exceeded (%v)", tr.timeout),
132132
}
133133
default:
134134
}
135135

136-
req := Request{
137-
ID: test.ID,
138-
Method: test.Method,
139-
Params: test.Params,
140-
}
141-
142-
err := tr.SendRequest(req)
136+
err := tr.SendRequest(test.Request)
143137
if err != nil {
144138
return SingleTestResult{
145-
TestID: test.ID,
139+
TestID: test.Request.ID,
146140
Passed: false,
147141
Message: fmt.Sprintf("Failed to send request: %v", err),
148142
}
@@ -151,36 +145,29 @@ func (tr *TestRunner) runTest(ctx context.Context, test TestCase) SingleTestResu
151145
resp, err := tr.ReadResponse()
152146
if err != nil {
153147
return SingleTestResult{
154-
TestID: test.ID,
148+
TestID: test.Request.ID,
155149
Passed: false,
156150
Message: fmt.Sprintf("Failed to read response: %v", err),
157151
}
158152
}
159153

160154
if err := validateResponse(test, resp); err != nil {
161155
return SingleTestResult{
162-
TestID: test.ID,
156+
TestID: test.Request.ID,
163157
Passed: false,
164158
Message: fmt.Sprintf("Invalid response: %s", err.Error()),
165159
}
166160
}
167161
return SingleTestResult{
168-
TestID: test.ID,
162+
TestID: test.Request.ID,
169163
Passed: true,
170164
}
171165
}
172166

173167
// validateResponse validates that a response matches the expected test outcome.
174-
// Returns an error if:
175-
//
176-
// Response ID does not match the request ID
177-
// Response does not match the expected outcome (error or success)
168+
// Returns an error if the response does not match the expected outcome (error or success).
178169
func validateResponse(test TestCase, resp *Response) error {
179-
if resp.ID != test.ID {
180-
return fmt.Errorf("response ID mismatch: expected %s, got %s", test.ID, resp.ID)
181-
}
182-
183-
if test.Expected.Error != nil {
170+
if test.ExpectedResponse.Error != nil {
184171
return validateResponseForError(test, resp)
185172
}
186173

@@ -191,14 +178,14 @@ func validateResponse(test TestCase, resp *Response) error {
191178
// It ensures the response contains an error, the result is null or omitted, and if an
192179
// error code is expected, it matches the expected type and member.
193180
func validateResponseForError(test TestCase, resp *Response) error {
194-
if test.Expected.Error == nil {
181+
if test.ExpectedResponse.Error == nil {
195182
panic("validateResponseForError expects non-nil error")
196183
}
197184

198185
if resp.Error == nil {
199-
if test.Expected.Error.Code != nil {
186+
if test.ExpectedResponse.Error.Code != nil {
200187
return fmt.Errorf("expected error %s.%s, but got no error",
201-
test.Expected.Error.Code.Type, test.Expected.Error.Code.Member)
188+
test.ExpectedResponse.Error.Code.Type, test.ExpectedResponse.Error.Code.Member)
202189
}
203190
return fmt.Errorf("expected error, but got no error")
204191
}
@@ -207,18 +194,18 @@ func validateResponseForError(test TestCase, resp *Response) error {
207194
return fmt.Errorf("expected result to be null or omitted when error is present, got: %s", string(resp.Result))
208195
}
209196

210-
if test.Expected.Error.Code != nil {
197+
if test.ExpectedResponse.Error.Code != nil {
211198
if resp.Error.Code == nil {
212199
return fmt.Errorf("expected error code %s.%s, but got error with no code",
213-
test.Expected.Error.Code.Type, test.Expected.Error.Code.Member)
200+
test.ExpectedResponse.Error.Code.Type, test.ExpectedResponse.Error.Code.Member)
214201
}
215202

216-
if resp.Error.Code.Type != test.Expected.Error.Code.Type {
217-
return fmt.Errorf("expected error type %s, got %s", test.Expected.Error.Code.Type, resp.Error.Code.Type)
203+
if resp.Error.Code.Type != test.ExpectedResponse.Error.Code.Type {
204+
return fmt.Errorf("expected error type %s, got %s", test.ExpectedResponse.Error.Code.Type, resp.Error.Code.Type)
218205
}
219206

220-
if resp.Error.Code.Member != test.Expected.Error.Code.Member {
221-
return fmt.Errorf("expected error member %s, got %s", test.Expected.Error.Code.Member, resp.Error.Code.Member)
207+
if resp.Error.Code.Member != test.ExpectedResponse.Error.Code.Member {
208+
return fmt.Errorf("expected error member %s, got %s", test.ExpectedResponse.Error.Code.Member, resp.Error.Code.Member)
222209
}
223210
}
224211
return nil
@@ -228,7 +215,7 @@ func validateResponseForError(test TestCase, resp *Response) error {
228215
// It ensures the response contains no error, and if a result is expected, it matches the
229216
// expected value.
230217
func validateResponseForSuccess(test TestCase, resp *Response) error {
231-
if test.Expected.Error != nil {
218+
if test.ExpectedResponse.Error != nil {
232219
panic("validateResponseForSuccess expects nil error")
233220
}
234221

@@ -239,14 +226,14 @@ func validateResponseForSuccess(test TestCase, resp *Response) error {
239226
return fmt.Errorf("expected success with no error, but got error")
240227
}
241228

242-
if test.Expected.Result.IsNullOrOmitted() {
229+
if test.ExpectedResponse.Result.IsNullOrOmitted() {
243230
if !resp.Result.IsNullOrOmitted() {
244231
return fmt.Errorf("expected null or omitted result, got: %s", string(resp.Result))
245232
}
246233
return nil
247234
}
248235

249-
expectedNorm, err := test.Expected.Result.Normalize()
236+
expectedNorm, err := test.ExpectedResponse.Result.Normalize()
250237
if err != nil {
251238
return fmt.Errorf("failed to normalize expected result: %w", err)
252239
}

0 commit comments

Comments
 (0)