| 91 | } |
| 92 | |
| 93 | TEST(ParserObjectWriteHostTest, ParserWritesToBothHostsFromOneParse) { |
| 94 | // Host setup: one scalar topic + one object topic. |
| 95 | DataEngine engine; |
| 96 | auto dataset_or = engine.createDataset(DatasetDescriptor{.source_name = "t", .time_domain_id = 0}); |
| 97 | ASSERT_TRUE(dataset_or.has_value()) << dataset_or.error(); |
| 98 | PJ_data_source_handle_t source_handle{static_cast<uint32_t>(*dataset_or)}; |
| 99 | |
| 100 | // Scalar: ensure topic + DatastoreParserWriteHost bound to it. |
| 101 | DatastoreSourceWriteHost scalar_impl(engine, source_handle); |
| 102 | auto scalar_view = sdk::SourceWriteHostView{scalar_impl.raw()}; |
| 103 | const auto topic = *scalar_view.ensureTopic("media_topic"); |
| 104 | DatastoreParserWriteHost parser_write_impl(engine, topic); |
| 105 | |
| 106 | // Object: register topic in ObjectStore; bind DatastoreParserObjectWriteHost. |
| 107 | ObjectStore store; |
| 108 | DatastoreSourceObjectWriteHost obj_source(store, *dataset_or); |
| 109 | const auto obj_topic = |
| 110 | *sdk::SourceObjectWriteHostView{obj_source.raw()}.registerTopic("media_topic", R"({"media_class":"image"})"); |
| 111 | DatastoreParserObjectWriteHost parser_obj_impl(store, obj_topic.id); |
| 112 | |
| 113 | // Build the registry with both services. |
| 114 | MockRegistryState registry_state; |
| 115 | const auto scalar_raw = parser_write_impl.raw(); |
| 116 | const auto obj_raw = parser_obj_impl.raw(); |
| 117 | registry_state.services[ParserWriteHostService::kName] = PJ_service_t{scalar_raw.ctx, scalar_raw.vtable}; |
| 118 | registry_state.services[ParserObjectWriteHostService::kName] = PJ_service_t{obj_raw.ctx, obj_raw.vtable}; |
| 119 | |
| 120 | static const PJ_service_registry_vtable_t registry_vtable = { |
| 121 | PJ_PLUGIN_DATA_API_VERSION, |
| 122 | sizeof(PJ_service_registry_vtable_t), |
| 123 | mockGetService, |
| 124 | }; |
| 125 | const PJ_service_registry_t registry_raw{®istry_state, ®istry_vtable}; |
| 126 | |
| 127 | // Bind the parser through the SDK. |
| 128 | MediaParser parser; |
| 129 | ASSERT_TRUE(parser.bind(sdk::ServiceRegistry{registry_raw}).has_value()); |
| 130 | |
| 131 | // parse() one message: seq=7, payload=[0xAA 0xBB 0xCC]. |
| 132 | std::vector<uint8_t> payload(sizeof(uint64_t) + 3); |
| 133 | uint64_t seq = 7; |
| 134 | std::memcpy(payload.data(), &seq, sizeof(uint64_t)); |
| 135 | payload[sizeof(uint64_t) + 0] = 0xAA; |
| 136 | payload[sizeof(uint64_t) + 1] = 0xBB; |
| 137 | payload[sizeof(uint64_t) + 2] = 0xCC; |
| 138 | |
| 139 | ASSERT_TRUE(parser.parse(100, Span<const uint8_t>(payload.data(), payload.size())).has_value()); |
| 140 | |
| 141 | // Object-store side: bytes landed. |
| 142 | auto resolved = store.latestAt(ObjectTopicId{obj_topic.id}, 100); |
| 143 | ASSERT_TRUE(resolved.has_value()); |
| 144 | ASSERT_NE(resolved->payload.anchor, nullptr); |
| 145 | const std::vector<uint8_t> expected{0xAA, 0xBB, 0xCC}; |
| 146 | EXPECT_TRUE( |
| 147 | std::equal(resolved->payload.bytes.begin(), resolved->payload.bytes.end(), expected.begin(), expected.end())); |
| 148 | |
| 149 | // (Scalar side requires flushing + a read path; Phase-3 scope is proving |
| 150 | // both hosts were resolved and invoked. Scalar writes go into DataEngine |
nothing calls this directly
no test coverage detected