Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions behavior-model.json
Original file line number Diff line number Diff line change
Expand Up @@ -1781,6 +1781,23 @@
503
]
}
},
"UpdateTopic": {
"empty_on": [
302,
303
],
"idempotent": true,
"readonly": false,
"retry": {
"backoff": "exponential",
"base_delay_ms": 1000,
"max": 2,
"retry_on": [
429,
503
]
}
}
},
"version": "1.0.0"
Expand Down
13 changes: 13 additions & 0 deletions conformance/runner/go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,13 @@ func runTest(tc TestCase) TestResult {

// Create generated client pointing to mock server with auth header
credentials := newConformanceCredentials(tc)
httpClient := &http.Client{
CheckRedirect: func(*http.Request, []*http.Request) error {
return http.ErrUseLastResponse
},
}
client, err := generated.NewClient(server.URL,
generated.WithHTTPClient(httpClient),
generated.WithAuthRefresher(credentials.refresh),
generated.WithRequestEditorFn(func(ctx context.Context, req *http.Request) error {
token, _ := credentials.AccessToken(ctx)
Expand Down Expand Up @@ -504,6 +510,7 @@ type checkState struct {
// "empty" (no result) rather than error. See ADR-004.
var emptyOnOperations = map[string][]int{
"GetOngoingTimeTrack": {404},
"UpdateTopic": {302, 303},
}

func isEmptyOnStatus(operation string, statusCode int) bool {
Expand Down Expand Up @@ -1409,6 +1416,10 @@ func executeOperation(client *generated.Client, ctx context.Context, tc TestCase
topicId := getInt64Param(tc.PathParams, "topicId")
body := generated.MoveTopicJSONRequestBody{BoxId: getInt64Param(tc.RequestBody, "box_id")}
return client.MoveTopic(ctx, topicId, body)
case "UpdateTopic":
topicId := getInt64Param(tc.PathParams, "topicId")
body := generated.UpdateTopicJSONRequestBody{Name: getStringParam(tc.RequestBody, "name")}
return client.UpdateTopic(ctx, topicId, body)

// Entry status and forwards
case "MarkEntrySpam":
Expand Down Expand Up @@ -1660,6 +1671,8 @@ func executeHEYOperation(client *hey.Client, ctx context.Context, tc TestCase) (
case "SendDraft":
entryID := getInt64Param(tc.PathParams, "entryId")
return nil, client.Messages().SendDraft(ctx, entryID, draftContentParam(tc.RequestBody))
case "UpdateTopic":
return nil, client.Topics().Update(ctx, getInt64Param(tc.PathParams, "topicId"), getStringParam(tc.RequestBody, "name"))
default:
return nil, fmt.Errorf("HEY client conformance does not support operation: %s", tc.Operation)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import com.basecamp.hey.generated.models.MarkPostingsRequestContent
import com.basecamp.hey.generated.models.MessagePayload
import com.basecamp.hey.generated.models.MoveStickyRequestContent
import com.basecamp.hey.generated.models.MoveTopicRequestContent
import com.basecamp.hey.generated.models.UpdateTopicRequestContent
import com.basecamp.hey.generated.models.MoveWorkflowStagingRequestContent
import com.basecamp.hey.generated.models.MovePostingsRequestContent
import com.basecamp.hey.generated.models.ReplyMessagePayload
Expand Down Expand Up @@ -390,6 +391,11 @@ private suspend fun executeOperation(client: HeyClient, case: TestCase): Outcome
Outcome.Unit
}

"UpdateTopic" -> {
client.topics.update(path.int64("topicId"), UpdateTopicRequestContent(name = body.string("name")))
Outcome.Unit
}

"MarkEntrySpam" -> {
client.entries.markSpam(path.int64("entryId"))
Outcome.Unit
Expand Down
10 changes: 10 additions & 0 deletions conformance/runner/rust/src/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,16 @@ async fn execute_operation(client: &Client, case: &TestCase) -> Result<Outcome,
.await?;
Ok(Outcome::Unit)
}
"UpdateTopic" => {
let request = models::UpdateTopicRequestContent {
name: string_param(body, "name"),
};
client
.topics()
.update(int64_param(path, "topicId"), &request)
.await?;
Ok(Outcome::Unit)
}

"MarkEntrySpam" => {
client
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ private func executeOperation(_ client: HeyClient, _ testCase: TestCase) async t
try await client.topics.moveTopic(topicId: path.int("topicId"), body: MoveTopicRequestContent(boxId: body.int("box_id")))
return .unit

case "UpdateTopic":
try await client.topics.update(topicId: path.int("topicId"), body: UpdateTopicRequestContent(name: body.string("name")))
return .unit

case "MarkEntrySpam":
try await client.entries.markSpam(entryId: path.int("entryId"))
return .unit
Expand Down
45 changes: 45 additions & 0 deletions conformance/tests/paths.json
Original file line number Diff line number Diff line change
Expand Up @@ -2128,6 +2128,51 @@
"topics"
]
},
{
"name": "UpdateTopic uses /topics/{topicId} path",
"description": "Verifies UpdateTopic PATCHes /topics/{topicId} with flat name body; 302 is success and must not be followed",
"operation": "UpdateTopic",
"method": "PATCH",
"path": "/topics/{topicId}",
"pathParams": {
"topicId": 456
},
"requestBody": {
"name": "Amex charge"
},
"mockResponses": [
{
"status": 302,
"headers": {
"Location": "/topics/456"
},
"body": {}
}
],
"assertions": [
{
"type": "requestPath",
"expected": "/topics/456"
},
{
"type": "requestMethod",
"expected": "PATCH"
},
{
"type": "requestBody",
"expected": {
"name": "Amex charge"
}
},
{
"type": "noError"
}
],
"tags": [
"path",
"topics"
]
},
{
"name": "MarkEntrySpam uses /entries/{entryId}/status/spam.json path",
"description": "Verifies that MarkEntrySpam constructs the URL path /entries/{entryId}/status/spam.json",
Expand Down
Loading