CoolProp 8.0.0
An open-source fluid property and humid air property database
CoolProp-Tests-SchemaValidation.cpp
Go to the documentation of this file.
1// Catch2 tests for CoolProp::validate_json_against_schema and
2// CoolProp::to_canonical_json_str (CoolProp-bh2).
3
4#if defined(ENABLE_CATCH)
5
6# include <catch2/catch_all.hpp>
7
8# include <string>
9
11# include "CoolProp/Exceptions.h"
12
13namespace {
14
15// Common test schema modelled after the SVDSBTL options schema we're
16// about to write — exercises objects, nested objects, enums, types,
17// required keys, and additionalProperties:false (strict mode).
18constexpr const char* kSchema = R"({
19 "$schema": "http://json-schema.org/draft-07/schema#",
20 "type": "object",
21 "additionalProperties": false,
22 "required": ["schema"],
23 "properties": {
24 "schema": {"type": "integer", "const": 1},
25 "grid": {
26 "type": "object",
27 "additionalProperties": false,
28 "properties": {
29 "NT": {"type": "integer", "minimum": 2},
30 "NR": {"type": "integer", "minimum": 2},
31 "rank": {"type": "integer", "minimum": 1}
32 }
33 },
34 "critical_patch": {
35 "type": "object",
36 "additionalProperties": false,
37 "properties": {
38 "mode": {"type": "string", "enum": ["auto", "off", "fixed"]},
39 "tolerance": {"type": "number", "exclusiveMinimum": 0},
40 "bbox": {
41 "type": "array",
42 "minItems": 4,
43 "maxItems": 4,
44 "items": {"type": "number"}
45 }
46 }
47 }
48 }
49})";
50
51} // namespace
52
53// ---------------------------------------------------------------------------
54// validate_json_against_schema
55// ---------------------------------------------------------------------------
56
57TEST_CASE("validate_against_schema: minimal valid instance passes", "[SchemaValidation]") {
58 REQUIRE_NOTHROW(CoolProp::validate_json_against_schema(R"({"schema": 1})", std::string(kSchema)));
59}
60
61TEST_CASE("validate_against_schema: fully populated valid instance passes", "[SchemaValidation]") {
63 "schema": 1,
64 "grid": {"NT": 200, "NR": 800, "rank": 20},
65 "critical_patch": {"mode": "auto", "tolerance": 1e-3, "bbox": [0.95, 1.05, 0.75, 1.15]}
66 })",
67 std::string(kSchema)));
68}
69
70TEST_CASE("validate_against_schema: unknown top-level key throws", "[SchemaValidation]") {
71 REQUIRE_THROWS_AS(CoolProp::validate_json_against_schema(R"({"schema": 1, "frobnitz": true})", std::string(kSchema)), CoolProp::ValueError);
72}
73
74TEST_CASE("validate_against_schema: unknown nested key throws", "[SchemaValidation]") {
75 REQUIRE_THROWS_AS(CoolProp::validate_json_against_schema(R"({"schema": 1, "grid": {"NT": 200, "extra": 3}})", std::string(kSchema)),
77}
78
79TEST_CASE("validate_against_schema: type mismatch throws", "[SchemaValidation]") {
80 REQUIRE_THROWS_AS(CoolProp::validate_json_against_schema(R"({"schema": 1, "grid": {"NT": "two-hundred"}})", std::string(kSchema)),
82}
83
84TEST_CASE("validate_against_schema: enum violation throws", "[SchemaValidation]") {
85 REQUIRE_THROWS_AS(CoolProp::validate_json_against_schema(R"({"schema": 1, "critical_patch": {"mode": "ON"}})", std::string(kSchema)),
87}
88
89TEST_CASE("validate_against_schema: required key missing throws", "[SchemaValidation]") {
90 REQUIRE_THROWS_AS(CoolProp::validate_json_against_schema(R"({"grid": {"NT": 200}})", std::string(kSchema)), CoolProp::ValueError);
91}
92
93TEST_CASE("validate_against_schema: bbox wrong size throws", "[SchemaValidation]") {
94 REQUIRE_THROWS_AS(CoolProp::validate_json_against_schema(R"({"schema": 1, "critical_patch": {"bbox": [1, 2, 3]}})", std::string(kSchema)),
96}
97
98TEST_CASE("validate_against_schema: invalid schema JSON throws", "[SchemaValidation]") {
99 REQUIRE_THROWS_AS(CoolProp::validate_json_against_schema(R"({"schema": 1})", std::string("not json")), CoolProp::ValueError);
100}
101
102// ---------------------------------------------------------------------------
103// to_canonical_json_str
104// ---------------------------------------------------------------------------
105
106TEST_CASE("to_canonical_json: sorts object keys", "[SchemaValidation]") {
107 const auto a = CoolProp::to_canonical_json_str(R"({"b": 2, "a": 1, "c": 3})");
108 REQUIRE(a == R"({"a":1,"b":2,"c":3})");
109}
110
111TEST_CASE("to_canonical_json: sorts nested object keys recursively", "[SchemaValidation]") {
112 const auto a = CoolProp::to_canonical_json_str(R"({"outer": {"z": 1, "a": 2}, "inner": 3})");
113 REQUIRE(a == R"({"inner":3,"outer":{"a":2,"z":1}})");
114}
115
116TEST_CASE("to_canonical_json: preserves array order", "[SchemaValidation]") {
117 const auto a = CoolProp::to_canonical_json_str(R"([3, 1, 2])");
118 REQUIRE(a == R"([3,1,2])");
119}
120
121TEST_CASE("to_canonical_json: logically-equal inputs produce identical output", "[SchemaValidation]") {
122 const auto a = CoolProp::to_canonical_json_str(R"({"grid":{"NT":200,"rank":20,"NR":800}})");
123 const auto b = CoolProp::to_canonical_json_str(R"({"grid":{"rank":20,"NR":800,"NT":200}})");
124 REQUIRE(a == b);
125}
126
127TEST_CASE("to_canonical_json: idempotent — re-canonicalising returns the same bytes", "[SchemaValidation]") {
128 const auto a = CoolProp::to_canonical_json_str(R"({"b":2,"a":1})");
129 const auto b = CoolProp::to_canonical_json_str(a);
130 REQUIRE(a == b);
131}
132
133TEST_CASE("to_canonical_json: handles all JSON scalar types", "[SchemaValidation]") {
134 const auto a = CoolProp::to_canonical_json_str(R"({"i":1,"f":1.5,"s":"x","b":true,"n":null,"a":[1,2]})");
135 // Sorted: a, b, f, i, n, s
136 REQUIRE(a == R"({"a":[1,2],"b":true,"f":1.5,"i":1,"n":null,"s":"x"})");
137}
138
139TEST_CASE("to_canonical_json: invalid JSON throws", "[SchemaValidation]") {
140 REQUIRE_THROWS_AS(CoolProp::to_canonical_json_str(std::string("{not: json}")), CoolProp::ValueError);
141}
142
143#endif // ENABLE_CATCH