fix: edit ops review findings

From the review of 7723711:
- The assembler is locked: a client closed for a full queue is closed
  by the task that was sending, loop(), whose disconnect dropped the
  buffer while async_tcp could be copying into it. A whole message is
  taken out of it, so a disconnect can't free it while it is read.
- loop() expires a message stalled for 10 s, freeing its buffer and
  telling its client, instead of holding it until another client came.
  A binary message is refused before it can touch another's buffer.
- Each edit takes only the fields its route reads; one it would ignore
  is refused by name. linkRemote's rolling code goes in under the
  route's name, so applyModelEdit is unchanged again.
- An edit refused for an id that doesn't exist replies not found
  (edit_result_t::missing). A reply that doesn't fit its buffer goes
  as an internal error, so the op still gets one.
- The TYPE table says what becomes of each op (proto_op_t), which the
  socket switches on. The reply address is one proto_reply_to_t. The
  routes and the ops start and queue an edit the same way
  (newModelEdit, submitModelEdit). The full-list texts and the offered
  remote address rule live in ModelEditor.
- e2e: the sort order test skips a list that it would renumber.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-24 18:10:06 +02:00
parent 77237111ea
commit 8fa087aa89
23 changed files with 486 additions and 257 deletions

View file

@ -50,6 +50,26 @@ static void test_type_names_are_unique() {
std::set<std::string> names;
for(uint8_t i = 0; i < protoTypeCount(); i++) TEST_ASSERT_TRUE_MESSAGE(names.insert(protoTypeAt(i).name).second, protoTypeAt(i).name);
}
static void test_every_op_says_what_becomes_of_it() {
for(uint8_t i = 0; i < protoTypeCount(); i++) {
const proto_named_t &t = protoTypeAt(i);
const bool op = t.number >= 16 && t.number <= 127;
TEST_ASSERT_EQUAL_MESSAGE(op, protoOpKind(t.number) != proto_op_t::none, t.name);
}
TEST_ASSERT_EQUAL(proto_op_t::shadeRequest, protoOpKind(static_cast<uint8_t>(proto_type_t::shadeCommand)));
TEST_ASSERT_EQUAL(proto_op_t::remoteFrame, protoOpKind(static_cast<uint8_t>(proto_type_t::sendRemoteCommand)));
TEST_ASSERT_EQUAL(proto_op_t::edit, protoOpKind(static_cast<uint8_t>(proto_type_t::saveShade)));
TEST_ASSERT_EQUAL(proto_op_t::read, protoOpKind(static_cast<uint8_t>(proto_type_t::groupOptions)));
TEST_ASSERT_EQUAL(proto_op_t::none, protoOpKind(250));
}
static void test_a_field_by_its_key() {
proto_field_t f;
TEST_ASSERT_TRUE(protoFieldOfKey("s", f));
TEST_ASSERT_EQUAL(proto_field_t::shadeId, f);
TEST_ASSERT_TRUE(protoFieldOfKey("vc", f));
TEST_ASSERT_EQUAL(proto_field_t::validation, f);
TEST_ASSERT_FALSE(protoFieldOfKey("zz", f));
}
static void test_error_and_feed_numbers_are_unique() {
std::set<uint8_t> errors, feeds;
for(uint8_t i = 0; i < protoErrorCount(); i++) TEST_ASSERT_TRUE(errors.insert(protoErrorAt(i).number).second);
@ -386,6 +406,8 @@ int main(int argc, char **argv) {
RUN_TEST(test_every_field_has_a_known_kind);
RUN_TEST(test_type_numbers_are_unique_and_in_their_sender_range);
RUN_TEST(test_type_names_are_unique);
RUN_TEST(test_every_op_says_what_becomes_of_it);
RUN_TEST(test_a_field_by_its_key);
RUN_TEST(test_error_and_feed_numbers_are_unique);
RUN_TEST(test_bootstrap_keys_are_fixed);
RUN_TEST(test_schema_hash_is_eight_hex_digits_and_stable);