Plan M4b: approvals over Mattermost in 15 tasks
Skeletons for new files only; existing files change by exact edits, so no task overwrites an earlier one's work. Each task's tests were run against a reference built in order from master, with the gate at each step and mutations checked; each skeleton compiles and fails its tests, and every todo!() comment was read beside its signature. The reference is kept off this machine. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
# M4b task 09: editing a post, and a username
|
||||
|
||||
**Branch:** `m4b` (run `git switch m4b`; `git status --short` must be empty, otherwise stop)
|
||||
**Commit subject:** `gatewayd: mm, patch_post and username`
|
||||
|
||||
## Goal
|
||||
|
||||
When an approval ends, `gatewayd` edits its post to say how, naming who answered (M4b spec,
|
||||
section 6). Two REST calls, checked against Mattermost v11.11.0, and confirmed on the owner's
|
||||
server (the bot may edit its own posts):
|
||||
|
||||
- `PUT /api/v4/posts/{post_id}/patch` with `{"message": "…"}`: replaces the post's text; the
|
||||
answer is the post.
|
||||
- `GET /api/v4/users/{user_id}`: the user, with `username`.
|
||||
|
||||
Every id that goes into a path must be a Mattermost id first, or nothing is sent.
|
||||
|
||||
## Files
|
||||
|
||||
- Copy: `crates/gatewayd/tests/mm_edit.rs`
|
||||
- Modify: `crates/gatewayd/src/mm/rest.rs`, `docs/implementer-log.md`
|
||||
|
||||
## The changes, exactly
|
||||
|
||||
In `crates/gatewayd/src/mm/rest.rs`, after `struct Channel`:
|
||||
|
||||
```rust
|
||||
#[derive(Deserialize)]
|
||||
struct User {
|
||||
username: String,
|
||||
}
|
||||
|
||||
/// A Mattermost username: 1 to 64 characters of `a-z`, `0-9`, `.`, `-` and `_`.
|
||||
fn valid_username(name: &str) -> bool {
|
||||
(1..=64).contains(&name.len())
|
||||
&& name.bytes().all(|b| {
|
||||
b.is_ascii_lowercase() || b.is_ascii_digit() || matches!(b, b'.' | b'-' | b'_')
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
and two methods at the end of `impl Client`, written like `posts_since` and `create_post`:
|
||||
|
||||
1. `pub fn patch_post(&self, post: &str, message: &str) -> Result<(), MmError>`:
|
||||
- `post` not `valid_id`: `Err(MmError::Json("a post path must be a Mattermost id".to_string()))`,
|
||||
nothing sent.
|
||||
- `PUT` `/api/v4/posts/{post}/patch` with the body
|
||||
`serde_json::json!({ "message": message }).to_string()`, through `self.call`.
|
||||
- The answer is a `Post`: `json::<Post>(&body)?.check().map(|_| ())`.
|
||||
2. `pub fn username(&self, user: &str) -> Result<String, MmError>`:
|
||||
- `user` not `valid_id`: `Err(MmError::Json("a user path must be a Mattermost id".to_string()))`.
|
||||
- `GET` `/api/v4/users/{user}` into `User`; a username that is not `valid_username` is
|
||||
`Err(MmError::Json("the user has no valid username".to_string()))`. Else `Ok(username)`.
|
||||
|
||||
## Steps
|
||||
|
||||
- [ ] **1. Copy.** `git switch m4b`, then `cp docs/plans/M4b/files/crates/gatewayd/tests/mm_edit.rs crates/gatewayd/tests/`
|
||||
- [ ] **2. See it fail.** `cargo test -p gatewayd --test mm_edit`. Expected: it does not compile.
|
||||
- [ ] **3. Make the changes.** Run `cargo fmt --all`.
|
||||
- [ ] **4. See it pass.** `cargo test -p gatewayd --test mm_edit --test mm_rest`. Expected: 4 and
|
||||
10 passed.
|
||||
- [ ] **5. Run the gate.** `make gate`. Expected last line: `gate: ok`.
|
||||
- [ ] **6. Log and commit.** `git add crates/gatewayd docs/implementer-log.md Cargo.lock && git commit`
|
||||
|
||||
## Done when
|
||||
|
||||
- Both suites pass; `make gate` prints `gate: ok`.
|
||||
Reference in New Issue
Block a user