// orm_hook: Model save()/remove() reach the audit Appender through the // PATCHES section 2 hook. Without an installed Appender the hook no-ops; // with one installed, insert/update/delete append self-describing records. #include #include #include #include "audit/log/appender.h" #include "audit/log/store.h" #include "data/orm/model.hpp" #define CHECK(c, msg) \ if (!(c)) { \ std::cerr << "FAIL orm_hook: " << msg << "\n"; \ return 1; \ } namespace { using molo::data::orm::FieldDef; using molo::data::orm::Model; struct Item : public Model { static std::string table_name() { return "items"; } static std::string primary_key() { return "id"; } static std::vector fields() { return {FieldDef::primary_key("id"), FieldDef::text("name")}; } }; class FakeConn : public molo::data::Connection { public: molo::data::Backend backend() const override { return molo::data::Backend::Sqlite; } std::string backend_id() const override { return "fake"; } molo::data::Result exec(const molo::data::Context&, const molo::data::BoundStatement&) override { return molo::data::Result::command(0); } molo::data::Prepared prepare(const molo::data::Context&, const molo::data::BoundStatement&) override { return {}; } molo::data::Result exec_prepared(const molo::data::Context&, const molo::data::Prepared&, const std::vector&) override { return molo::data::Result::command(0); } void ping(const molo::data::Context&) override {} void close() override {} }; } // namespace int main() { using namespace molo::audit::log; FakeConn conn; molo::data::Context ctx("req-1", "t1", 30); // Without an installed Appender the hook no-ops (no crash, no store). { Item it; it.set("id", molo::data::make_int(7)); it.set("name", molo::data::make_text("n1")); it.save(conn, ctx); it.set("name", molo::data::make_text("n2")); it.save(conn, ctx); it.remove(conn, ctx); } MemoryStore store; Appender app(store); Appender::Install(&app); { Item it; it.set("id", molo::data::make_int(7)); it.set("name", molo::data::make_text("n1")); it.save(conn, ctx); it.set("name", molo::data::make_text("n2")); it.save(conn, ctx); it.remove(conn, ctx); } Appender::Install(nullptr); auto rows = store.ReadRange(1, 10); CHECK(rows.size() == 3, "three hook records"); CHECK(rows[0].op == "insert" && rows[0].table == "items" && rows[0].pk == "7", "insert record shape"); CHECK(rows[0].tenant == "t1" && rows[0].request_id == "req-1", "context carried"); CHECK(rows[0].after_json.find("n1") != std::string::npos, "insert after image"); CHECK(rows[1].op == "update", "update op"); CHECK(rows[2].op == "delete" && rows[2].after_json.empty(), "delete op"); CHECK(rows[2].before_json.find("n2") != std::string::npos, "delete before image"); std::cout << "PASS orm_hook\n"; return 0; }