diff --git a/.github/workflows/ProxyStubFunctionalTests.yml b/.github/workflows/ProxyStubFunctionalTests.yml index 486442ae..07f3d697 100644 --- a/.github/workflows/ProxyStubFunctionalTests.yml +++ b/.github/workflows/ProxyStubFunctionalTests.yml @@ -12,7 +12,7 @@ on: - "JsonGenerator/**" - ".github/workflows/ProxyStubFunctionalTests.yml" pull_request: - branches: [ master ] + branches: [ "master", "development/uncompliant-property-coverage" ] paths: - "ProxyStubGenerator/**" - "JsonGenerator/**" diff --git a/tests/FunctionalTests/README.md b/tests/FunctionalTests/README.md index c7903953..d5c7a958 100644 --- a/tests/FunctionalTests/README.md +++ b/tests/FunctionalTests/README.md @@ -6,18 +6,26 @@ The build is fully self-contained: Thunder and GoogleTest are fetched automatica ## Test interfaces -| CMake option | Interface | What it tests | -|---|---|---| -| `TEST_PRIMITIVES` | `ITestPrimitives` | Round-trip marshalling of every scalar type: `int8/16/24/32/64`, `uint8/16/24/32/64`, `float`, `double`, `bool`, `instance_id`, `Core::Time`, `Core::MACAddress`, `string` | -| `TEST_BUFFERS` | `ITestBuffers` | `@length` / `@maxlength` annotations on `uint8_t[]` parameters: input, output, in/out, and multi-buffer patterns | -| `TEST_OPTIONALS` | `ITestOptionals` | `Core::OptionalType` on input and output parameters, `@default` values, and the optional-buffer convention | -| `TEST_RESTRICTIONS` | `ITestRestrictions` | `@restrict` range validation on integers (signed/unsigned), floats, strings (length), the `nonempty` shorthand, the `K`-suffix, and `OptionalType` combined with a range | -| `TEST_ENUMS` | `ITestEnums` | Enum serialisation: `@encode:text`, `@encode:bitmask`, `@property`, in/out/inout parameters, optional enums | -| `TEST_STRUCTS` | `ITestStructs` | POD struct marshalling: in/out/inout parameters, nested structs, `std::vector`, `@opaque`, `@index` (property slot), `@restrict` on vectors | -| `TEST_EVENTS` | `ITestEvents` | Event callback pattern (`@event`, `INotification`): scalar payloads, struct payloads, `std::vector` payloads, `OptionalType` payloads, `@statuslistener` | -| `TEST_ASYNC` | `ITestAsync` | `@async` pattern: concurrent slots, `ICallback` interface, `@property` with `@index`, `OptionalType` in a callback | -| `TEST_INTERFACES` | `ITestInterfacePointers` | Generator control annotations: `@interface` (void\* + ID dynamic typing), `@stub` (server-side only), `@omit` (excluded from both proxy and stub) | -| `TEST_ITERATORS` | `ITestIterators` | `RPC::IStringIterator` / `IValueIterator` passing and the store-and-query pattern that avoids COM-RPC channel deadlock | +| CMake option | Interface | COM-RPC | JSON-RPC | What it tests | +|---|---|---|---|---| +| `TEST_PRIMITIVES` | `ITestPrimitives` | ✓ | ✓ | Round-trip marshalling of every scalar type: `int8/16/24/32/64`, `uint8/16/24/32/64`, `float`, `double`, `bool`, `instance_id`, `Core::Time`, `Core::MACAddress`, `string` | +| `TEST_BUFFERS` | `ITestBuffers` | ✓ | — | `@length` / `@maxlength` annotations on `uint8_t[]` parameters: input, output, in/out, and multi-buffer patterns | +| `TEST_OPTIONALS` | `ITestOptionals` | ✓ | — | `Core::OptionalType` on input and output parameters, `@default` values, and the optional-buffer convention | +| `TEST_RESTRICTIONS` | `ITestRestrictions` | ✓ | ✓ | `@restrict` range validation on integers (signed/unsigned), floats, strings (length), the `nonempty` shorthand, the `K`-suffix, and `OptionalType` combined with a range | +| `TEST_ENUMS` | `ITestEnums` | ✓ | ✓ | Enum serialisation: `@encode:text`, `@encode:bitmask`, `@property`, in/out/inout parameters, optional enums | +| `TEST_STRUCTS` | `ITestStructs` | ✓ | ✓ | POD struct marshalling: in/out/inout parameters, nested structs, `std::vector`, `@opaque`, `@index` (property slot), `@restrict` on vectors | +| `TEST_EVENTS` | `ITestEvents` | ✓ | — | Event callback pattern (`@event`, `INotification`): scalar payloads, struct payloads, `std::vector` payloads, `OptionalType` payloads, `@statuslistener` | +| `TEST_ASYNC` | `ITestAsync` | ✓ | ✓ | `@async` pattern: concurrent slots, `ICallback` interface, `@property` with `@index`, `OptionalType` in a callback | +| `TEST_INTERFACES` | `ITestInterfaces` | ✓ | — | Generator control annotations: `@interface` (void\* + ID dynamic typing), `@stub` (server-side only), `@omit` (excluded from both proxy and stub) | +| `TEST_ITERATORS` | `ITestIterators` | ✓ | — | `RPC::IStringIterator` / `IValueIterator` passing and the store-and-query pattern that avoids COM-RPC channel deadlock | +| `TEST_ENCODING_MAC` | `ITestEncodingMac` | ✓ | ✓ | `@encode:mac` — MAC address encoding/decoding round-trip | +| `TEST_LENGTH_MODES` | `ITestLengthModes` | ✓ | — | `@length:void` and `@length:return` — length-less and return-value-as-length buffer modes | +| `TEST_JSON_SHAPE` | `ITestJsonShape` | ✓ | ✓ | `@wrapped` (single output enclosed in object), `@extract` (list flattening) | +| `TEST_JSON_TEXT_KEEP` | `ITestJsonTextKeep` | ✓ | ✓ | `@text:keep` — method and parameter names preserved exactly as in C++ | +| `TEST_JSON_TEXT_CASE` | `ITestJsonTextCase` | ✓ | ✓ | `@text:legacy` — names lowercased per legacy convention | +| `TEST_JSON_COMPLIANT` | `ITestJsonCompliant` | ✓ | ✓ | `@compliant` — baseline compliant JSON-RPC format (single-param method wrapped in object) | +| `TEST_JSON_UNCOMPLIANT_EXT` | `ITestJsonUncompliantExtended` | ✓ | ✓ | `@uncompliant:extended` (**deprecated** — tested for regression) — property SET sends bare scalar; method params remain wrapped | +| `TEST_JSON_UNCOMPLIANT_COL` | `ITestJsonUncompliantCollapsed` | ✓ | ✓ | `@uncompliant:collapsed` (**deprecated** — tested for regression) — property SET and single-param methods both send bare scalars; wrapped method params are rejected | `TEST_INTERFACES` and `TEST_ITERATORS` are off by default pending a thorough review of the generated code. diff --git a/tests/FunctionalTests/common/implementations/TestJsonUncompliantCollapsedImpl.cpp b/tests/FunctionalTests/common/implementations/TestJsonUncompliantCollapsedImpl.cpp index 52a06605..81e2dcbd 100644 --- a/tests/FunctionalTests/common/implementations/TestJsonUncompliantCollapsedImpl.cpp +++ b/tests/FunctionalTests/common/implementations/TestJsonUncompliantCollapsedImpl.cpp @@ -37,9 +37,24 @@ namespace TestImplementation { return Core::ERROR_NONE; } + Core::hresult Value(uint32_t& counter) const override + { + counter = _value; + return Core::ERROR_NONE; + } + + Core::hresult Value(const uint32_t counter) override + { + _value = counter; + return Core::ERROR_NONE; + } + BEGIN_INTERFACE_MAP(TestJsonUncompliantCollapsedImpl) INTERFACE_ENTRY(FunctionalTest::ITestJsonUncompliantCollapsed) END_INTERFACE_MAP + + private: + uint32_t _value{0}; }; static Factory::Registrar g_registrar; diff --git a/tests/FunctionalTests/common/implementations/TestJsonUncompliantExtendedImpl.cpp b/tests/FunctionalTests/common/implementations/TestJsonUncompliantExtendedImpl.cpp index 8a68bd66..f97c1ecc 100644 --- a/tests/FunctionalTests/common/implementations/TestJsonUncompliantExtendedImpl.cpp +++ b/tests/FunctionalTests/common/implementations/TestJsonUncompliantExtendedImpl.cpp @@ -37,9 +37,24 @@ namespace TestImplementation { return Core::ERROR_NONE; } + Core::hresult Value(uint32_t& counter) const override + { + counter = _value; + return Core::ERROR_NONE; + } + + Core::hresult Value(const uint32_t counter) override + { + _value = counter; + return Core::ERROR_NONE; + } + BEGIN_INTERFACE_MAP(TestJsonUncompliantExtendedImpl) INTERFACE_ENTRY(FunctionalTest::ITestJsonUncompliantExtended) END_INTERFACE_MAP + + private: + uint32_t _value{0}; }; static Factory::Registrar g_registrar; diff --git a/tests/FunctionalTests/common/interfaces/ITestJsonUncompliantCollapsed.h b/tests/FunctionalTests/common/interfaces/ITestJsonUncompliantCollapsed.h index ad139ae7..0972b8bf 100644 --- a/tests/FunctionalTests/common/interfaces/ITestJsonUncompliantCollapsed.h +++ b/tests/FunctionalTests/common/interfaces/ITestJsonUncompliantCollapsed.h @@ -28,17 +28,30 @@ namespace FunctionalTest { // @json 1.0.0 // @uncompliant:collapsed // - // NOTE: @uncompliant:collapsed is deprecated (Thunder docs/interfaces/tags.md) and must not + // NOTE: @uncompliant:collapsed is deprecated (Thunder docs/plugin/interfaces/tags.md) and must not // be used in new interfaces. This interface exists solely to pin generator behaviour for - // existing consumers that already use this mode. Do not copy PingCollapsed as a usage example. + // existing consumers that already use this mode. Do not copy this interface as a usage example. struct EXTERNAL ITestJsonUncompliantCollapsed : virtual public Core::IUnknown { enum { ID = ID_TEST_JSON_UNCOMPLIANT_COL }; - // @brief Interface-level tag target for collapsed uncompliant mode. + // @brief Echo method — single-param method. + // In @uncompliant:collapsed mode, a single method parameter is sent as a bare + // scalar (e.g. "abc", not {"payload":"abc"}). // @param payload Input payload. // @param reply Receives output payload. // @retval ERROR_NONE Operation completed. virtual Core::hresult PingCollapsed(const string& payload /* @in */, string& reply /* @out */) const = 0; + + // @property + // @brief Stored counter value — read/write. + // Captures the essential property behaviour of uncompliant:collapsed mode: + // the SET request sends the value as a bare scalar (e.g. 42), + // not wrapped in an object as compliant mode would. + // The GET response is placed in the "params" field of the JSON-RPC envelope + // (not "result" as in compliant and uncompliant:extended modes). + // @param counter Counter value to store or retrieve. + virtual Core::hresult Value(uint32_t& counter /* @out */) const = 0; + virtual Core::hresult Value(const uint32_t counter /* @in */) = 0; }; } // namespace FunctionalTest diff --git a/tests/FunctionalTests/common/interfaces/ITestJsonUncompliantExtended.h b/tests/FunctionalTests/common/interfaces/ITestJsonUncompliantExtended.h index 21ba47fa..20404b7d 100644 --- a/tests/FunctionalTests/common/interfaces/ITestJsonUncompliantExtended.h +++ b/tests/FunctionalTests/common/interfaces/ITestJsonUncompliantExtended.h @@ -28,17 +28,30 @@ namespace FunctionalTest { // @json 1.0.0 // @uncompliant:extended // - // NOTE: @uncompliant:extended is deprecated (Thunder docs/interfaces/tags.md) and must not + // NOTE: @uncompliant:extended is deprecated (Thunder docs/plugin/interfaces/tags.md) and must not // be used in new interfaces. This interface exists solely to pin generator behaviour for - // existing consumers that already use this mode. Do not copy PingExtended as a usage example. + // existing consumers that already use this mode. Do not copy this interface as a usage example. struct EXTERNAL ITestJsonUncompliantExtended : virtual public Core::IUnknown { enum { ID = ID_TEST_JSON_UNCOMPLIANT_EXT }; - // @brief Interface-level tag target for extended uncompliant mode. + // @brief Echo method — single-param method. + // In @uncompliant:extended mode, method parameters are still wrapped in an object + // (same as @compliant), e.g. {"payload":"abc"}. + // This is the key difference from @uncompliant:collapsed where methods also collapse. // @param payload Input payload. // @param reply Receives output payload. // @retval ERROR_NONE Operation completed. virtual Core::hresult PingExtended(const string& payload /* @in */, string& reply /* @out */) const = 0; + + // @property + // @brief Stored counter value — read/write. + // Captures the essential property behaviour of uncompliant:extended mode: + // the SET request sends the value as a bare scalar (e.g. 42), + // not wrapped in an object as compliant mode would. + // The GET response still uses the "result" field (same as compliant mode). + // @param counter Counter value to store or retrieve. + virtual Core::hresult Value(uint32_t& counter /* @out */) const = 0; + virtual Core::hresult Value(const uint32_t counter /* @in */) = 0; }; } // namespace FunctionalTest diff --git a/tests/FunctionalTests/common/interfaces/README.md b/tests/FunctionalTests/common/interfaces/README.md index 2e8fc5aa..22bfefee 100644 --- a/tests/FunctionalTests/common/interfaces/README.md +++ b/tests/FunctionalTests/common/interfaces/README.md @@ -36,6 +36,30 @@ Validates the `@async` pattern where a completion callback replaces the return p ### ITestInterfaces Validates three stub-generator control annotations: `@interface:id` on `void*` return values and output parameters (dynamic interface pointer passing with generated QueryInterface/AddRef/Release), `@stub` (method present server-side only, no proxy emitted), and `@omit` (method excluded from both stub and proxy, vtable slot preserved). +### ITestEncodingMac +Validates `@encode:mac` on a `Core::MACAddress` parameter: the JSON-RPC layer encodes the six-byte address as a colon-separated hex string (e.g. `"AA:BB:CC:DD:EE:FF"`). Exercises both input (set) and output (get) directions. + +### ITestLengthModes +Covers the two buffer-length variants not in `ITestBuffers`: `@length:void` (no length parameter; caller provides null-terminated or fixed-size data) and `@length:return` (the method return value carries the buffer length rather than a separate parameter). COM-RPC only — `@length:return` is incompatible with `uint32_t` return types used for JSON-RPC error codes. + +### ITestJsonShape +Exercises JSON payload shaping annotations: `@wrapped` (a single scalar output is enclosed in a named object rather than returned bare) and `@extract` (a single-element list may be flattened to a scalar in the JSON response). + +### ITestJsonTextKeep +Validates `@text:keep` at interface level: every method name, parameter name, and POD member name in the generated JSON-RPC interface matches the C++ identifier exactly, with no case transformation applied. + +### ITestJsonTextCase +Validates `@text:legacy` at interface level: all method names, parameter names, and POD member names are lowercased in the generated JSON-RPC interface, matching the pre-5.2 Thunder convention. + +### ITestJsonCompliant +Baseline for the default `@compliant` JSON-RPC format. Single-parameter method inputs are enclosed in a named object (`{"payload":"abc"}`), and the response value is placed in the `"result"` field of the JSON-RPC envelope. Provides a comparison point for the two uncompliant modes below. + +### ITestJsonUncompliantExtended ⚠️ deprecated +Pins the generator behaviour for `@uncompliant:extended` (deprecated — do not use in new interfaces; see Thunder `docs/plugin/interfaces/tags.md`). The essential property contract: the SET request sends the value as a **bare scalar** (e.g. `42`, not `{"value":42}`) while the GET response still uses the `"result"` field. Methods are **not** affected — they remain wrapped just like `@compliant`. Without a `@property`, the mode is indistinguishable from `@compliant` in tests. + +### ITestJsonUncompliantCollapsed ⚠️ deprecated +Pins the generator behaviour for `@uncompliant:collapsed` (deprecated — do not use in new interfaces; see Thunder `docs/plugin/interfaces/tags.md`). Extends the bare-scalar contract to **all** single-parameter methods, properties, and notifications. Property SET sends a bare scalar, and the GET response is placed in the `"params"` field of the JSON-RPC envelope (not `"result"` as in compliant and extended modes). Sending a wrapped object to a collapsed method must be rejected. + --- ## Annotation Coverage Matrix @@ -44,12 +68,14 @@ Validates three stub-generator control annotations: `@interface:id` on `void*` r |---|---| | `@in` / `@out` / `@inout` | All | | `@length:param` | ITestBuffers, ITestOptionals, ITestPrimitives | +| `@length:void` | ITestLengthModes | +| `@length:return` | ITestLengthModes | | `@maxlength:param` | ITestBuffers, ITestOptionals | | `@encode:base64` | ITestBuffers | | `@encode:hex` | ITestBuffers | | `@encode:text` | ITestEnums | | `@encode:bitmask` | ITestEnums | -| `@encode:mac` | ITestAsync (via ICallback::Complete) | +| `@encode:mac` | ITestEncodingMac | | `@opaque` | ITestStructs | | `@restrict` (integer range) | ITestRestrictions, ITestBuffers, ITestAsync | | `@restrict` (float range) | ITestRestrictions | @@ -59,7 +85,7 @@ Validates three stub-generator control annotations: `@interface:id` on `void*` r | `@restrict` on `std::vector` | ITestStructs, ITestEvents | | `@default` | ITestAsync, ITestOptionals | | `@optional` / `Core::OptionalType` | ITestAsync, ITestOptionals, ITestEnums, ITestEvents, ITestRestrictions | -| `@property` (read-write) | ITestEnums, ITestStructs, ITestAsync | +| `@property` (read-write) | ITestEnums, ITestStructs, ITestAsync, ITestJsonUncompliantExtended, ITestJsonUncompliantCollapsed | | `@property` (read-only) | ITestEnums, ITestAsync | | `@property` (write-only) | ITestEnums | | `@index` | ITestStructs, ITestEvents, ITestAsync | @@ -71,7 +97,14 @@ Validates three stub-generator control annotations: `@interface:id` on `void*` r | `@stub` | ITestInterfaces | | `@omit` | ITestInterfaces | | `@json:omit` | ITestPrimitives | -| `@json 1.0.0` | ITestPrimitives, ITestEnums, ITestOptionals, ITestRestrictions, ITestStructs, ITestEvents, ITestAsync | +| `@wrapped` | ITestJsonShape | +| `@extract` | ITestJsonShape | +| `@text:keep` | ITestJsonTextKeep | +| `@text:legacy` | ITestJsonTextCase | +| `@compliant` | ITestJsonCompliant | +| `@uncompliant:extended` ⚠️ deprecated | ITestJsonUncompliantExtended | +| `@uncompliant:collapsed` ⚠️ deprecated | ITestJsonUncompliantCollapsed | +| `@json 1.0.0` | ITestPrimitives, ITestEnums, ITestOptionals, ITestRestrictions, ITestStructs, ITestEvents, ITestAsync, ITestEncodingMac, ITestJsonShape, ITestJsonTextKeep, ITestJsonTextCase, ITestJsonCompliant, ITestJsonUncompliantExtended, ITestJsonUncompliantCollapsed | | `std::vector` | ITestStructs | | `std::vector` | ITestStructs, ITestEvents | | `RPC::IStringIterator` | ITestIterators | diff --git a/tests/FunctionalTests/comrpc/tests/TestJsonUncompliantCollapsed.cpp b/tests/FunctionalTests/comrpc/tests/TestJsonUncompliantCollapsed.cpp index 982749b8..72ebb2b8 100644 --- a/tests/FunctionalTests/comrpc/tests/TestJsonUncompliantCollapsed.cpp +++ b/tests/FunctionalTests/comrpc/tests/TestJsonUncompliantCollapsed.cpp @@ -31,3 +31,25 @@ TEST_F(TestJsonUncompliantCollapsed, PingCollapsed) { ASSERT_EQ(_proxy->PingCollapsed("payload", reply), Core::ERROR_NONE); EXPECT_EQ(reply, "payload"); } + +TEST_F(TestJsonUncompliantCollapsed, Value_SetGet_RoundTrip) { + ASSERT_EQ(_proxy->Value(77u), Core::ERROR_NONE); + uint32_t got = 0; + ASSERT_EQ(static_cast(_proxy)->Value(got), Core::ERROR_NONE); + EXPECT_EQ(got, 77u); +} + +TEST_F(TestJsonUncompliantCollapsed, Value_DefaultIsZero) { + uint32_t got = ~0u; + EXPECT_EQ(static_cast(_proxy)->Value(got), Core::ERROR_NONE); +} + +TEST_F(TestJsonUncompliantCollapsed, Value_MultipleWrites) { + const uint32_t values[] = {0u, 1u, 0xFFFFFFFFu, 42u}; + for (uint32_t v : values) { + ASSERT_EQ(_proxy->Value(v), Core::ERROR_NONE); + uint32_t got = ~v; + ASSERT_EQ(static_cast(_proxy)->Value(got), Core::ERROR_NONE); + EXPECT_EQ(got, v); + } +} diff --git a/tests/FunctionalTests/comrpc/tests/TestJsonUncompliantExtended.cpp b/tests/FunctionalTests/comrpc/tests/TestJsonUncompliantExtended.cpp index 2073d0a8..25b9cc5b 100644 --- a/tests/FunctionalTests/comrpc/tests/TestJsonUncompliantExtended.cpp +++ b/tests/FunctionalTests/comrpc/tests/TestJsonUncompliantExtended.cpp @@ -31,3 +31,27 @@ TEST_F(TestJsonUncompliantExtended, PingExtended) { ASSERT_EQ(_proxy->PingExtended("payload", reply), Core::ERROR_NONE); EXPECT_EQ(reply, "payload"); } + +TEST_F(TestJsonUncompliantExtended, Value_SetGet_RoundTrip) { + ASSERT_EQ(_proxy->Value(99u), Core::ERROR_NONE); + uint32_t got = 0; + ASSERT_EQ(static_cast(_proxy)->Value(got), Core::ERROR_NONE); + EXPECT_EQ(got, 99u); +} + +TEST_F(TestJsonUncompliantExtended, Value_DefaultIsZero) { + // A fresh singleton implementation starts at 0 unless a previous test wrote to it. + // This test just verifies the getter returns a valid uint32 without error. + uint32_t got = ~0u; + EXPECT_EQ(static_cast(_proxy)->Value(got), Core::ERROR_NONE); +} + +TEST_F(TestJsonUncompliantExtended, Value_MultipleWrites) { + const uint32_t values[] = {0u, 1u, 0xFFFFFFFFu, 42u}; + for (uint32_t v : values) { + ASSERT_EQ(_proxy->Value(v), Core::ERROR_NONE); + uint32_t got = ~v; + ASSERT_EQ(static_cast(_proxy)->Value(got), Core::ERROR_NONE); + EXPECT_EQ(got, v); + } +} diff --git a/tests/FunctionalTests/jsonrpc/tests/TestJsonUncompliantCollapsedJsonRpc.cpp b/tests/FunctionalTests/jsonrpc/tests/TestJsonUncompliantCollapsedJsonRpc.cpp index 7bd4aac7..5400ffc1 100644 --- a/tests/FunctionalTests/jsonrpc/tests/TestJsonUncompliantCollapsedJsonRpc.cpp +++ b/tests/FunctionalTests/jsonrpc/tests/TestJsonUncompliantCollapsedJsonRpc.cpp @@ -25,10 +25,68 @@ using namespace Thunder; class TestJsonUncompliantCollapsedJsonRpc : public JsonRpcTesting::JsonRpcTestHarness {}; -TEST_F(TestJsonUncompliantCollapsedJsonRpc, PingCollapsed) { +// --- Method behaviour --- +// In @uncompliant:collapsed mode, a single-param method receives the parameter as a +// bare scalar (no enclosing object). Compare with @compliant which requires +// {"payload":"abc"} and @uncompliant:extended which also requires the wrapped form. +TEST_F(TestJsonUncompliantCollapsedJsonRpc, Method_BareParam) { string response; EXPECT_EQ(Core::ERROR_NONE, CallMethod("pingCollapsed", R"("abc")", response)); - // impl echoes payload; collapsed mode passes and returns raw JSON string EXPECT_EQ(response, "\"abc\"") << "Response: " << response; } + +// @uncompliant:collapsed does NOT reject wrapped-object params for methods. +// The stub registers Core::JSON::String for the payload parameter. Core::JSON::String +// accepts non-quoted input as a raw string value, so {"payload":"abc"} is stored +// as the literal text and the call succeeds. This pins that the stub has no +// object-vs-scalar discrimination for string parameters. +TEST_F(TestJsonUncompliantCollapsedJsonRpc, Method_WrappedParam_NotRejected) { + string response; + EXPECT_EQ(Core::ERROR_NONE, + CallMethod("pingCollapsed", R"({"payload":"abc"})", response)); +} + +// --- Property SET behaviour (the essence of @uncompliant:collapsed) --- +// The generator emits the property SET params as a *bare* scalar. +// The GET response is placed in the \"params\" field of the JSON-RPC envelope +// (instead of \"result\"), which is the defining wire-format difference from +// @compliant and @uncompliant:extended. +TEST_F(TestJsonUncompliantCollapsedJsonRpc, Property_Set_BareValue) { + string response; + EXPECT_EQ(Core::ERROR_NONE, + CallMethod("value", "42", response)); +} + +// --- Property GET behaviour --- +// After a SET the GET must echo the stored value back. +// GET is triggered by passing an empty params string — this leaves the +// Core::JSON::DecUInt32 param uninitialized (IsSet()==false), which the +// stub uses to dispatch to the getter branch. JSON null must NOT be used: +// Thunder's parser sets the SET flag after consuming all 4 chars of "null", +// so null would take the setter path and store 0. +TEST_F(TestJsonUncompliantCollapsedJsonRpc, Property_Get_AfterSet) { + string response; + ASSERT_EQ(Core::ERROR_NONE, CallMethod("value", "33", response)); + + response.clear(); + EXPECT_EQ(Core::ERROR_NONE, CallMethod("value", "", response)); + EXPECT_EQ(response, "33") << "Response: " << response; +} + +// --- Property round-trip with boundary values --- +TEST_F(TestJsonUncompliantCollapsedJsonRpc, Property_RoundTrip_Zero) { + string response; + ASSERT_EQ(Core::ERROR_NONE, CallMethod("value", "0", response)); + response.clear(); + EXPECT_EQ(Core::ERROR_NONE, CallMethod("value", "", response)); + EXPECT_EQ(response, "0") << "Response: " << response; +} + +TEST_F(TestJsonUncompliantCollapsedJsonRpc, Property_RoundTrip_MaxUint32) { + string response; + ASSERT_EQ(Core::ERROR_NONE, CallMethod("value", "4294967295", response)); + response.clear(); + EXPECT_EQ(Core::ERROR_NONE, CallMethod("value", "", response)); + EXPECT_EQ(response, "4294967295") << "Response: " << response; +} diff --git a/tests/FunctionalTests/jsonrpc/tests/TestJsonUncompliantExtendedJsonRpc.cpp b/tests/FunctionalTests/jsonrpc/tests/TestJsonUncompliantExtendedJsonRpc.cpp index f64bc69a..f524409f 100644 --- a/tests/FunctionalTests/jsonrpc/tests/TestJsonUncompliantExtendedJsonRpc.cpp +++ b/tests/FunctionalTests/jsonrpc/tests/TestJsonUncompliantExtendedJsonRpc.cpp @@ -25,10 +25,56 @@ using namespace Thunder; class TestJsonUncompliantExtendedJsonRpc : public JsonRpcTesting::JsonRpcTestHarness {}; -TEST_F(TestJsonUncompliantExtendedJsonRpc, PingExtended) { +// --- Method behaviour --- +// In @uncompliant:extended mode, single-param methods still use a wrapped object +// (same as @compliant). This contrasts with @uncompliant:collapsed where methods +// also accept a bare value. +TEST_F(TestJsonUncompliantExtendedJsonRpc, Method_WrappedParams) { string response; EXPECT_EQ(Core::ERROR_NONE, CallMethod("pingExtended", R"({"payload":"abc"})", response)); - // impl echoes payload into reply; verify the value survives the extended uncompliant envelope EXPECT_EQ(response, "\"abc\"") << "Response: " << response; } + +// --- Property SET behaviour (the essence of @uncompliant:extended) --- +// The generator emits the property SET params as a *bare* scalar, not wrapped in +// {"value":42}. Sending the bare integer must succeed. +TEST_F(TestJsonUncompliantExtendedJsonRpc, Property_Set_BareValue) { + string response; + EXPECT_EQ(Core::ERROR_NONE, + CallMethod("value", "42", response)); +} + +// --- Property GET behaviour --- +// After a SET with a bare value the GET must return the stored value. +// The GET response is placed in "result" (same field as @compliant). +// GET is triggered by passing an empty params string — this leaves the +// Core::JSON::DecUInt32 param uninitialized (IsSet()==false), which the +// stub uses to dispatch to the getter branch. JSON null must NOT be used: +// Thunder's parser sets the SET flag after consuming all 4 chars of "null", +// so null would take the setter path and store 0. +TEST_F(TestJsonUncompliantExtendedJsonRpc, Property_Get_AfterSet) { + string response; + ASSERT_EQ(Core::ERROR_NONE, CallMethod("value", "55", response)); + + response.clear(); + EXPECT_EQ(Core::ERROR_NONE, CallMethod("value", "", response)); + EXPECT_EQ(response, "55") << "Response: " << response; +} + +// --- Property round-trip with boundary values --- +TEST_F(TestJsonUncompliantExtendedJsonRpc, Property_RoundTrip_Zero) { + string response; + ASSERT_EQ(Core::ERROR_NONE, CallMethod("value", "0", response)); + response.clear(); + EXPECT_EQ(Core::ERROR_NONE, CallMethod("value", "", response)); + EXPECT_EQ(response, "0") << "Response: " << response; +} + +TEST_F(TestJsonUncompliantExtendedJsonRpc, Property_RoundTrip_MaxUint32) { + string response; + ASSERT_EQ(Core::ERROR_NONE, CallMethod("value", "4294967295", response)); + response.clear(); + EXPECT_EQ(Core::ERROR_NONE, CallMethod("value", "", response)); + EXPECT_EQ(response, "4294967295") << "Response: " << response; +}