|
@@ -1108,6 +1108,18 @@ TEST_F(ApiFixture, RelationRejectsBadOnDelete) {
|
|
|
|
|
|
|
|
TEST_F(ApiFixture, DeleteBlockedByRelationReturns409WithImpacts) {
|
|
TEST_F(ApiFixture, DeleteBlockedByRelationReturns409WithImpacts) {
|
|
|
auto c = admin();
|
|
auto c = admin();
|
|
|
|
|
+ std::string relUrl = "/api/v1/projects/" + project_ + "/relations/inv_cust";
|
|
|
|
|
+
|
|
|
|
|
+ // RAII guard: this test creates a restrict relation that must not survive the
|
|
|
|
|
+ // test, since tmpName()'s project name is deterministic and dropProject does
|
|
|
|
|
+ // not purge collection data (see db_test_util.hpp). Runs on early ASSERT_*
|
|
|
|
|
+ // returns too. A missing relation on cleanup is fine (idempotent delete).
|
|
|
|
|
+ struct RelationGuard {
|
|
|
|
|
+ httplib::Client& c;
|
|
|
|
|
+ std::string url;
|
|
|
|
|
+ ~RelationGuard() { c.Delete(url.c_str()); }
|
|
|
|
|
+ } relGuard{c, relUrl};
|
|
|
|
|
+
|
|
|
std::string base = "/api/v1/projects/" + project_ + "/collections";
|
|
std::string base = "/api/v1/projects/" + project_ + "/collections";
|
|
|
ASSERT_EQ(c.Post(base.c_str(), nlohmann::json{{"name","inv"},{"kind","json"}}.dump(),
|
|
ASSERT_EQ(c.Post(base.c_str(), nlohmann::json{{"name","inv"},{"kind","json"}}.dump(),
|
|
|
"application/json")->status, 201);
|
|
"application/json")->status, 201);
|
|
@@ -1139,17 +1151,36 @@ TEST_F(ApiFixture, DeleteBlockedByRelationReturns409WithImpacts) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// relations_enforced is read on the collection being deleted FROM (the
|
|
// relations_enforced is read on the collection being deleted FROM (the
|
|
|
-// PARENT side of the relation) — Delete() checks
|
|
|
|
|
|
|
+// PARENT side of the relation) - Delete() checks
|
|
|
// config_manager_.configFor(request->collection()), i.e. the parent's own
|
|
// config_manager_.configFor(request->collection()), i.e. the parent's own
|
|
|
// flag. Disabling it on the CHILD collection has no effect on deletes of
|
|
// flag. Disabling it on the CHILD collection has no effect on deletes of
|
|
|
// the parent's documents: the child's relations_enforced only governs the
|
|
// the parent's documents: the child's relations_enforced only governs the
|
|
|
// child's own reverse-index arming / validate_on_write, a separate
|
|
// child's own reverse-index arming / validate_on_write, a separate
|
|
|
// mechanism. So the toggle here targets "cust2" (the parent), not "inv2"
|
|
// mechanism. So the toggle here targets "cust2" (the parent), not "inv2"
|
|
|
-// (the child) — this is deliberately non-obvious and every API consumer
|
|
|
|
|
|
|
+// (the child) - this is deliberately non-obvious and every API consumer
|
|
|
// will trip on it otherwise (see task-8-report.md for the source trace).
|
|
// will trip on it otherwise (see task-8-report.md for the source trace).
|
|
|
TEST_F(ApiFixture, RelationsEnforcedOnParentPermitsDelete) {
|
|
TEST_F(ApiFixture, RelationsEnforcedOnParentPermitsDelete) {
|
|
|
auto c = admin();
|
|
auto c = admin();
|
|
|
std::string base = "/api/v1/projects/" + project_ + "/collections";
|
|
std::string base = "/api/v1/projects/" + project_ + "/collections";
|
|
|
|
|
+ std::string relUrl = "/api/v1/projects/" + project_ + "/relations/inv2_cust2";
|
|
|
|
|
+ std::string enforcedUrl = base + "/cust2/relations-enforced";
|
|
|
|
|
+
|
|
|
|
|
+ // RAII guard: this test creates a restrict relation and flips relations_enforced
|
|
|
|
|
+ // to false on cust2 - both must be undone unconditionally, since tmpName()'s
|
|
|
|
|
+ // project name is deterministic and dropProject does not purge collection data
|
|
|
|
|
+ // (see db_test_util.hpp). Runs on early ASSERT_* returns too. A missing relation
|
|
|
|
|
+ // on cleanup is fine (idempotent delete); restoring enforced=true is idempotent too.
|
|
|
|
|
+ struct RelationEnforcedGuard {
|
|
|
|
|
+ httplib::Client& c;
|
|
|
|
|
+ std::string relUrl;
|
|
|
|
|
+ std::string enforcedUrl;
|
|
|
|
|
+ ~RelationEnforcedGuard() {
|
|
|
|
|
+ c.Delete(relUrl.c_str());
|
|
|
|
|
+ c.Put(enforcedUrl.c_str(), nlohmann::json{{"enforced", true}}.dump(),
|
|
|
|
|
+ "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+ } relGuard{c, relUrl, enforcedUrl};
|
|
|
|
|
+
|
|
|
ASSERT_EQ(c.Post(base.c_str(), nlohmann::json{{"name","inv2"},{"kind","json"}}.dump(),
|
|
ASSERT_EQ(c.Post(base.c_str(), nlohmann::json{{"name","inv2"},{"kind","json"}}.dump(),
|
|
|
"application/json")->status, 201);
|
|
"application/json")->status, 201);
|
|
|
ASSERT_EQ(c.Post(base.c_str(), nlohmann::json{{"name","cust2"},{"kind","json"}}.dump(),
|
|
ASSERT_EQ(c.Post(base.c_str(), nlohmann::json{{"name","cust2"},{"kind","json"}}.dump(),
|