|
@@ -1094,3 +1094,68 @@ TEST_F(ApiFixture, RelationRejectsBadOnDelete) {
|
|
|
{"parent","c"}, {"on_delete","explode"}}.dump(), "application/json");
|
|
{"parent","c"}, {"on_delete","explode"}}.dump(), "application/json");
|
|
|
ASSERT_TRUE(bad); EXPECT_EQ(bad->status, 422);
|
|
ASSERT_TRUE(bad); EXPECT_EQ(bad->status, 422);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+TEST_F(ApiFixture, DeleteBlockedByRelationReturns409WithImpacts) {
|
|
|
|
|
+ auto c = admin();
|
|
|
|
|
+ std::string base = "/api/v1/projects/" + project_ + "/collections";
|
|
|
|
|
+ ASSERT_EQ(c.Post(base.c_str(), nlohmann::json{{"name","inv"},{"kind","json"}}.dump(),
|
|
|
|
|
+ "application/json")->status, 201);
|
|
|
|
|
+ ASSERT_EQ(c.Post(base.c_str(), nlohmann::json{{"name","cust"},{"kind","json"}}.dump(),
|
|
|
|
|
+ "application/json")->status, 201);
|
|
|
|
|
+ std::string rel = "/api/v1/projects/" + project_ + "/relations";
|
|
|
|
|
+ ASSERT_EQ(c.Post(rel.c_str(), nlohmann::json{
|
|
|
|
|
+ {"name","inv_cust"}, {"child","inv"}, {"child_field","cust_id"},
|
|
|
|
|
+ {"parent","cust"}, {"on_delete","restrict"}}.dump(), "application/json")->status, 201);
|
|
|
|
|
+
|
|
|
|
|
+ auto p = c.Post((base + "/cust/documents").c_str(),
|
|
|
|
|
+ nlohmann::json{{"n","acme"}}.dump(), "application/json");
|
|
|
|
|
+ ASSERT_EQ(p->status, 201);
|
|
|
|
|
+ std::string pid = nlohmann::json::parse(p->body)["id"];
|
|
|
|
|
+ ASSERT_EQ(c.Post((base + "/inv/documents").c_str(),
|
|
|
|
|
+ nlohmann::json{{"cust_id", pid}}.dump(), "application/json")->status, 201);
|
|
|
|
|
+
|
|
|
|
|
+ auto impact = c.Get((base + "/cust/documents/" + pid + "/delete-impact").c_str());
|
|
|
|
|
+ ASSERT_TRUE(impact); EXPECT_EQ(impact->status, 200);
|
|
|
|
|
+ auto impactJson = nlohmann::json::parse(impact->body);
|
|
|
|
|
+ EXPECT_TRUE(impactJson["would_be_blocked"].get<bool>());
|
|
|
|
|
+ EXPECT_GE(impactJson["impacts"].size(), 1u);
|
|
|
|
|
+
|
|
|
|
|
+ auto del = c.Delete((base + "/cust/documents/" + pid).c_str());
|
|
|
|
|
+ ASSERT_TRUE(del); EXPECT_EQ(del->status, 409);
|
|
|
|
|
+ auto delJson = nlohmann::json::parse(del->body);
|
|
|
|
|
+ EXPECT_EQ(delJson["error"]["code"], "relation_restricted");
|
|
|
|
|
+ EXPECT_GE(delJson["error"]["details"]["impacts"].size(), 1u);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// relations_enforced is read on the collection being deleted FROM (the
|
|
|
|
|
+// PARENT side of the relation) — Delete() checks
|
|
|
|
|
+// config_manager_.configFor(request->collection()), i.e. the parent's own
|
|
|
|
|
+// 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
|
|
|
|
|
+// child's own reverse-index arming / validate_on_write, a separate
|
|
|
|
|
+// mechanism. So the toggle here targets "cust2" (the parent), not "inv2"
|
|
|
|
|
+// (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).
|
|
|
|
|
+TEST_F(ApiFixture, RelationsEnforcedOnParentPermitsDelete) {
|
|
|
|
|
+ auto c = admin();
|
|
|
|
|
+ std::string base = "/api/v1/projects/" + project_ + "/collections";
|
|
|
|
|
+ ASSERT_EQ(c.Post(base.c_str(), nlohmann::json{{"name","inv2"},{"kind","json"}}.dump(),
|
|
|
|
|
+ "application/json")->status, 201);
|
|
|
|
|
+ ASSERT_EQ(c.Post(base.c_str(), nlohmann::json{{"name","cust2"},{"kind","json"}}.dump(),
|
|
|
|
|
+ "application/json")->status, 201);
|
|
|
|
|
+ std::string rel = "/api/v1/projects/" + project_ + "/relations";
|
|
|
|
|
+ ASSERT_EQ(c.Post(rel.c_str(), nlohmann::json{
|
|
|
|
|
+ {"name","inv2_cust2"}, {"child","inv2"}, {"child_field","cust_id"},
|
|
|
|
|
+ {"parent","cust2"}, {"on_delete","restrict"}}.dump(), "application/json")->status, 201);
|
|
|
|
|
+
|
|
|
|
|
+ auto p = c.Post((base + "/cust2/documents").c_str(),
|
|
|
|
|
+ nlohmann::json{{"n","x"}}.dump(), "application/json");
|
|
|
|
|
+ std::string pid = nlohmann::json::parse(p->body)["id"];
|
|
|
|
|
+ ASSERT_EQ(c.Post((base + "/inv2/documents").c_str(),
|
|
|
|
|
+ nlohmann::json{{"cust_id", pid}}.dump(), "application/json")->status, 201);
|
|
|
|
|
+
|
|
|
|
|
+ auto off = c.Put((base + "/cust2/relations-enforced").c_str(),
|
|
|
|
|
+ nlohmann::json{{"enforced", false}}.dump(), "application/json");
|
|
|
|
|
+ ASSERT_TRUE(off); EXPECT_EQ(off->status, 200);
|
|
|
|
|
+ EXPECT_EQ(c.Delete((base + "/cust2/documents/" + pid).c_str())->status, 200);
|
|
|
|
|
+}
|