Browse Source

test: extension test (part 1)

Sam Jaffe 3 months ago
parent
commit
974edbac82
3 changed files with 134 additions and 1 deletions
  1. 11 1
      Makefile
  2. 37 0
      include/jvalidate/detail/relative_pointer.h
  3. 86 0
      tests/extension_test.cxx

+ 11 - 1
Makefile

@@ -23,7 +23,7 @@ HEADERS := $(shell find $(INCLUDE_DIR) -name *.h)
 TEST_HEADERS := $(wildcard $(TEST_DIR)*.h)
 TEST_SOURCES := $(wildcard $(TEST_DIR)*.cxx)
 TEST_OBJECTS := $(patsubst %.cxx, .build/%.o, $(TEST_SOURCES))
-TEST_BINARIES := .build/bin/selfvalidate .build/bin/annotation_test
+TEST_BINARIES := .build/bin/selfvalidate .build/bin/annotation_test .build/bin/extension_test
 EXECUTE_TESTS := $(patsubst %, %.done, $(TEST_BINARIES))
 
 EXCLUDED_TESTS := format* content ecmascript_regex zeroTerminatedFloats non_bmp_regex
@@ -67,3 +67,13 @@ run-test: $(EXECUTE_TESTS)
 .build/bin/annotation_test.done: .build/bin/annotation_test
 	.build/bin/annotation_test $(CLEAN_ANSI)
 	@ touch $@
+
+
+.build/bin/extension_test: .build/tests/extension_test.o
+	@ mkdir -p .build/bin
+	@ rm -f .build/test/extension_test.done
+	$(CXX) $< -o $@ $(LD_FLAGS) -ljsoncpp -lgmock -lgtest
+
+.build/bin/extension_test.done: .build/bin/extension_test
+	.build/bin/extension_test $(CLEAN_ANSI)
+	@ touch $@

+ 37 - 0
include/jvalidate/detail/relative_pointer.h

@@ -0,0 +1,37 @@
+#pragma once
+
+#include <string>
+#include <string_view>
+
+#include <jvalidate/detail/expect.h>
+#include <jvalidate/detail/pointer.h>
+#include <jvalidate/forward.h>
+
+namespace jvalidate::detail {
+class RelativePointer {
+public:
+  RelativePointer(std::string_view path) {
+    if (auto pos = path.find('/'); pos != path.npos) {
+      pointer_ = Pointer(path.substr(pos));
+      path.remove_suffix(path.size() - pos);
+    } else {
+      EXPECT_M(not path.empty() && path.back() == '#',
+               "RelativePointer must end in a pointer, or a '#'")
+      path.remove_suffix(1);
+    }
+    parent_steps_ = std::stoull(std::string(path));
+  }
+
+  template <Adapter A>
+  std::variant<std::string, A> inspect(Pointer const & where, A const & root) const {
+    if (pointer_) {
+      return pointer_->walk(where.parent(parent_steps_).walk(root));
+    }
+    return where.parent(parent_steps_).back();
+  }
+
+private:
+  size_t parent_steps_;
+  std::optional<Pointer> pointer_;
+};
+}

+ 86 - 0
tests/extension_test.cxx

@@ -0,0 +1,86 @@
+#include <jvalidate/constraint/extension_constraint.h>
+#include <jvalidate/detail/expect.h>
+#include <jvalidate/extension.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <json/reader.h>
+#include <json/value.h>
+
+#include <jvalidate/adapters/jsoncpp.h>
+#include <jvalidate/detail/relative_pointer.h>
+#include <jvalidate/status.h>
+#include <jvalidate/validator.h>
+
+using jvalidate::Status;
+using testing::Eq;
+
+Json::Value operator""_json(char const * data, size_t len) {
+  Json::Value value;
+
+  Json::CharReaderBuilder builder;
+  std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+
+  std::string error;
+  if (not reader->parse(data, data + len, &value, &error)) {
+    throw std::runtime_error(error);
+  }
+
+  return value;
+}
+
+struct IsKeyOfConstraint : jvalidate::extension::ConstraintBase<IsKeyOfConstraint> {
+  IsKeyOfConstraint(std::string_view ptr) : ptr(ptr) {
+    EXPECT_M(ptr.find('/') != std::string_view::npos,
+             "IsKeyOfConstraint requires a value-relative-pointer, not a key-relative-pointer");
+  }
+
+  jvalidate::detail::RelativePointer ptr;
+};
+
+template <jvalidate::Adapter A>
+class Visitor : public jvalidate::extension::Visitor<Visitor<A>, IsKeyOfConstraint> {
+public:
+  Visitor(A const & root_document) : root_document_(root_document) {}
+
+  template <jvalidate::Adapter A2>
+  Status visit(IsKeyOfConstraint const & cons, A2 const & document, auto const & validator) const {
+    auto const & object =
+        std::get<1>(cons.ptr.inspect(validator.where_, root_document_)).as_object();
+    if (object.find(document.as_string()) != object.end()) {
+      return Status::Accept;
+    }
+    return Status::Reject;
+  }
+
+private:
+  A const & root_document_;
+};
+
+TEST(ExtensionConstraint, CanExtend) {
+  jvalidate::schema::Node schema;
+  jvalidate::detail::StdRegexEngine re;
+
+  auto json = R"({
+    "nodes": {
+      "A": {},
+      "B": {}
+    },
+    "edges": [
+      { "source": "A", "destination": "B" }
+    ]
+  })"_json;
+  using Adapter = jvalidate::adapter::JsonCppAdapter<Json::Value>;
+  Adapter document = json;
+
+  jvalidate::ValidationVisitor validator(schema, {}, re, Visitor(document), nullptr);
+  jvalidate::constraint::ExtensionConstraint cons{std::make_unique<IsKeyOfConstraint>("1/nodes")};
+  EXPECT_THAT(validator.visit(cons, Adapter(json["edges"][0]["source"])), Eq(Status::Accept));
+  EXPECT_THAT(validator.visit(cons, Adapter(json["edges"][0]["destination"])), Eq(Status::Accept));
+}
+
+int main(int argc, char ** argv) {
+  testing::InitGoogleMock(&argc, argv);
+  return RUN_ALL_TESTS();
+}