ifw-daq  1.0.0
IFW Data Acquisition modules
testOcmDaqService.cpp
Go to the documentation of this file.
1 /**
2  * @file
3  * @ingroup daq_ocm_server_test
4  * @copyright 2021 ESO - European Southern Observatory
5  *
6  * @brief Unit tests for OcmDaqService
7  */
8 #include <ocmDaqService.hpp>
9 
10 #include <fmt/format.h>
11 #include <gtest/gtest.h>
12 
13 #include <daq/error.hpp>
14 #include <malMock.hpp>
15 #include <metadaqifMock.hpp>
16 #include <recifMock.hpp>
17 
18 #include "mock/managerMock.hpp"
19 #include "mock/ocmifMock.hpp"
20 #include "ocmifFake.hpp"
21 
22 using namespace ::testing;
23 using namespace std::literals;
24 
25 /**
26  * Fixture that sets up a OcmDaqService instance with following mockups:
27  * - mal::Mal
28  * - daq::Manager
29  *
30  * And prepared request string containing metadata sources
31  *
32  * @ingroup daq_ocm_server_test
33  */
34 struct TestOcmDaqService : Test {
36  : m_io_ctx()
37  , m_mal_mock()
38  , m_daq_impl(std::make_shared<OcmDaqService>(m_io_ctx, m_mal_mock, m_mgr_mock, "/tmp")) {
39  }
40  void SetUp() override {
41  m_metadata_sources = "meta@zpb.rr://uri";
42  m_prim_sources = "prim@zpb.rr://uri";
43  m_daq_properties = R"(
44  {
45  "keywords": [
46  {
47  "type": "esoKeyword",
48  "name": "FOO BAR",
49  "value": true
50  },
51  {
52  "type": "valueKeyword",
53  "name": "FOOBAR",
54  "value": true
55  }
56  ]
57  }
58  )";
59  }
60  void TearDown() override {
61  }
62 
63  boost::asio::io_context m_io_ctx; // NOLINT
64  MalMock m_mal_mock; // NOLINT
66  std::shared_ptr<OcmDaqService> m_daq_impl; // NOLINT
67  std::string m_prim_sources; // NOLINT
68  std::string m_metadata_sources; // NOLINT
69  std::string m_daq_properties; // NOLINT
70 };
71 
72 /**
73  * Fixture for testing when OcmDaqService is abandoned
74  * (e.g. when service is de-registered).
75  *
76  * @ingroup daq_ocm_server_test
77  */
79 
80 TEST(TestParseSingleSource, Successful) {
81  ParsedSource expected("name", "zpb://rr.uri");
82 
83  ParsedSource parsed =
84  ParseSourceUri(fmt::format("{}@{}", expected.name, expected.rr_uri));
85  EXPECT_EQ(expected, parsed);
86 }
87 
88 TEST(TestParseSingleSource, LeadingOrTrailingSpacesAreAllowed) {
89  ParsedSource expected("name", "zpb://rr.uri");
90  EXPECT_EQ(
91  expected,
92  ParseSourceUri(fmt::format("{}@{} ", expected.name, expected.rr_uri)));
93  EXPECT_EQ(
94  expected,
95  ParseSourceUri(fmt::format(" {}@{}", expected.name, expected.rr_uri)));
96  EXPECT_EQ(
97  expected,
98  ParseSourceUri(fmt::format(" {}@{} ", expected.name, expected.rr_uri)));
99 }
100 
101 TEST(TestParseSingleSource, Incomplete) {
102  ParsedSource expected("name", "zpb://rr.uri");
103  EXPECT_THROW(
104  ParseSourceUri(fmt::format("{}@,{} ", expected.name, expected.rr_uri)),
105  std::invalid_argument);
106  EXPECT_THROW(
107  ParseSourceUri(fmt::format("{}@,{} ", expected.name, expected.rr_uri)),
108  std::invalid_argument);
109  EXPECT_THROW(ParseSourceUri(fmt::format("@,")), std::invalid_argument);
110  EXPECT_THROW(ParseSourceUri(fmt::format("{},{}", expected.name, expected.rr_uri)),
111  std::invalid_argument);
112  EXPECT_THROW(ParseSourceUri(fmt::format("{}@,{}", expected.name, expected.rr_uri)),
113  std::invalid_argument);
114 }
115 
116 TEST(TestParseMultipleSources, Successful) {
117  ParsedSource expected1("name", "zpb://rr.uri");
118  ParsedSource expected2("name", "zpb://rr.uri");
119  std::vector<ParsedSource> result{expected1, expected2};
120 
121  EXPECT_EQ(result,
122  ParseSourceUris(fmt::format("{}@{} {}@{}",
123  expected1.name,
124  expected1.rr_uri,
125  expected2.name,
126  expected2.rr_uri)));
127  EXPECT_EQ(result,
128  ParseSourceUris(fmt::format("{}@{} {}@{}",
129  expected1.name,
130  expected1.rr_uri,
131  expected2.name,
132  expected2.rr_uri)));
133 
134  EXPECT_EQ(result,
135  ParseSourceUris(fmt::format(" {}@{} {}@{} ",
136  expected1.name,
137  expected1.rr_uri,
138  expected2.name,
139  expected2.rr_uri)));
140 }
141 
142 TEST(TestParseSingleSource, Empty) {
143  ParsedSource expected("name", "zpb://rr.uri");
144  EXPECT_THROW(ParseSourceUri(fmt::format("{}@,{} ", "", expected.rr_uri)),
145  std::invalid_argument);
146  EXPECT_THROW(ParseSourceUri(fmt::format("{}@,{} ", expected.name, "")),
147  std::invalid_argument);
148  EXPECT_THROW(ParseSourceUri(fmt::format("{}@,{} ", expected.name, expected.rr_uri)),
149  std::invalid_argument);
150 }
151 
152 TEST(TestParseDaqProperties, Successful) {
153  auto max_err_ms = 0.001;
154  {
155  auto properties_str = R"(
156  {
157  "keywords": [
158  {
159  "type": "esoKeyword",
160  "name": "FOO BAR",
161  "value": true
162  },
163  {
164  "type": "valueKeyword",
165  "name": "FOOBAR",
166  "value": true
167  }
168  ],
169  "awaitInterval": 0.1
170  }
171  )";
172  auto properties = ParseStartDaqProperties(properties_str);
173  EXPECT_THAT(static_cast<double>(properties.await_interval.count()),
174  DoubleNear(100, max_err_ms));
175  }
176  {
177  auto properties_str = R"(
178  {
179  "awaitInterval": 1
180  }
181  )";
182  auto properties = ParseStartDaqProperties(properties_str);
183  EXPECT_THAT(static_cast<double>(properties.await_interval.count()),
184  DoubleNear(1000, max_err_ms));
185  }
186 
187 }
188 
189 TEST(TestParseDaqProperties, Failures) {
190  EXPECT_THROW(ParseStartDaqProperties(R"({"awaitInterval": "0.1"})"),
191  std::invalid_argument);
192  EXPECT_THROW(ParseStartDaqProperties(R"({"awaitInterval": -0.1})"),
193  std::invalid_argument);
194 }
195 
196 TEST_F(TestOcmDaqService, StartDaqShouldTestIfIdAlreadyExistsAndReturnFailureIfItDoes) {
197  // Setup
198  // Setup so that ID already exist
199  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).WillOnce(Return(true));
200 
201  // Run
202  auto fut = m_daq_impl->StartDaq("id", "prefix", m_prim_sources, m_metadata_sources, "");
203  m_io_ctx.poll();
204  ASSERT_TRUE(fut.is_ready()) << "future should be ready by now since daq with id already exist";
205  EXPECT_THROW(fut.get(), ocmif::DaqException);
206 }
207 
208 TEST_F(TestOcmDaqService, StartDaqShouldCreateAndAssignIdIfNotProvided) {
209  // Setup
210  auto* mock_recif_client(new daq::RecCmdsAsyncMock);
211  auto* mock_metadaq_client(new daq::MetaDaqAsyncMock);
212  auto* fake_reply(new ocmif::DaqReplyFake);
213 
214  EXPECT_CALL(m_mgr_mock, MakeDaqId()).WillOnce(Return("id"));
215  EXPECT_CALL(m_mgr_mock, AddDaq(Pointee(Property(&daq::DaqController::GetId, "id"))));
216  EXPECT_CALL(m_mgr_mock, StartDaqAsync("id"sv))
217  .WillOnce(Return(ByMove(boost::make_ready_future<daq::State>(daq::State::Acquiring))));
218  EXPECT_CALL(m_mal_mock, getClientUnsafe(_, "metadaqif_MetaDaqAsync", _, _, _)).WillOnce(Return(mock_metadaq_client));
219  EXPECT_CALL(m_mal_mock, getClientUnsafe(_, "recif_RecCmdsAsync", _, _, _)).WillOnce(Return(mock_recif_client));
220  // Have to construct and return void* and still keep a reference to it..
221  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "ocmif_DaqReply"))
222  .WillOnce(Return(fake_reply))
223  .RetiresOnSaturation();
224 
225  // Run
226  auto fut = m_daq_impl->StartDaq("", "prefix", m_prim_sources, m_metadata_sources, "");
227  // Requred to trigger any .then continuations, even though future is ready
228  m_io_ctx.poll();
229 
230  ASSERT_TRUE(fut.is_ready()) << "future should be ready immediately since we faked a "
231  "synchronous start with make_ready_future";
232  EXPECT_TRUE(fut.has_value());
233  auto reply = fut.get();
234  ASSERT_TRUE(reply);
235  EXPECT_EQ(reply->getId(), "id");
236 }
237 
238 
239 TEST_F(TestOcmDaqService, StartDaqFailsIfJsonIsInvalid) {
240  // Setup
241 
242  // Run
243  auto fut = m_daq_impl->StartDaq("id", "prefix", "", m_metadata_sources, "INVALID JSON");
244  m_io_ctx.poll();
245  ASSERT_TRUE(fut.is_ready()) << "future should be ready by now since daq with id already exist";
246  EXPECT_THROW(fut.get(), ocmif::DaqException);
247 }
248 
249 TEST_F(TestOcmDaqService, StartDaqFailsIfJsonSchemaIsInvalid) {
250  // Setup
251 
252  // Run
253  // note: Expects JSON object
254  auto fut = m_daq_impl->StartDaq("id", "prefix", "", m_metadata_sources, "[]");
255  m_io_ctx.poll();
256  ASSERT_TRUE(fut.is_ready()) << "future should be ready by now since daq with id already exist";
257  EXPECT_THROW(fut.get(), ocmif::DaqException);
258 }
259 
260 TEST_F(TestOcmDaqService, StartDaqShouldAddAndStartDaqControllerIfArgumentsAreOk) {
261  // Setup
262  auto* mock_metadaq_client(new daq::MetaDaqAsyncMock);
263  auto* fake_reply(new ocmif::DaqReplyFake);
264 
265  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).WillOnce(Return(false));
266  EXPECT_CALL(m_mgr_mock, AddDaq(Pointee(Property(&daq::DaqController::GetId, "id"))));
267  EXPECT_CALL(m_mgr_mock, StartDaqAsync("id"sv))
268  .WillOnce(Return(ByMove(boost::make_ready_future<daq::State>(daq::State::Acquiring))));
269  EXPECT_CALL(m_mal_mock, getClientUnsafe(_, _, _, _, _)).WillOnce(Return(mock_metadaq_client));
270  // Have to construct and return void* and still keep a reference to it..
271  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "ocmif_DaqReply"))
272  .WillOnce(Return(fake_reply))
273  .RetiresOnSaturation();
274 
275  // Run
276  auto fut = m_daq_impl->StartDaq("id", "prefix", "", m_metadata_sources, m_daq_properties);
277 
278  // Requred to trigger any .then continuations, even though future is ready
279  m_io_ctx.poll();
280 
281  ASSERT_TRUE(fut.is_ready()) << "future should be ready immediately since we faked a "
282  "synchronous start with make_ready_future";
283  EXPECT_TRUE(fut.has_value());
284  auto reply = fut.get();
285  ASSERT_TRUE(reply);
286  EXPECT_EQ(reply->getId(), "id");
287 }
288 
289 TEST_F(TestOcmDaqService, StartDaqShouldFailIfAddDaqFails) {
290  // Setup
291  auto* mock_metadaq_client(new daq::MetaDaqAsyncMock);
292 
293  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).WillOnce(Return(false));
294  EXPECT_CALL(m_mgr_mock, AddDaq(Pointee(Property(&daq::DaqController::GetId, "id"))))
295  .WillOnce(Throw(std::runtime_error("error")));
296  EXPECT_CALL(m_mgr_mock, StartDaqAsync(_)).Times(0);
297  EXPECT_CALL(m_mal_mock, getClientUnsafe(_, _, _, _, _)).WillOnce(Return(mock_metadaq_client));
298 
299  // Run
300  auto fut = m_daq_impl->StartDaq("id", "prefix", "", m_metadata_sources, "");
301  m_io_ctx.poll();
302  ASSERT_TRUE(fut.is_ready()) << "future should be ready immediately since we faked a "
303  "synchronous start with make_ready_future";
304  EXPECT_THROW(fut.get(), ocmif::DaqException)
305  << "future should have contained the ICD exception type";
306 }
307 
308 TEST_F(TestOcmDaqService, StartDaqShouldFailIfManagerStartFails) {
309  // Setup
310  auto* mock_metadaq_client(new daq::MetaDaqAsyncMock);
311 
312  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).WillOnce(Return(false));
313  EXPECT_CALL(m_mgr_mock, AddDaq(Pointee(Property(&daq::DaqController::GetId, "id"))));
314  EXPECT_CALL(m_mgr_mock, StartDaqAsync("id"sv))
315  .WillOnce(Return(ByMove(boost::make_exceptional_future<daq::State>(
316  daq::DaqSourceErrors({daq::DaqSourceError("start", "source", "message")})))));
317  EXPECT_CALL(m_mal_mock, getClientUnsafe(_, _, _, _, _)).WillOnce(Return(mock_metadaq_client));
318 
319  // Run
320  auto fut = m_daq_impl->StartDaq("id", "prefix", "", m_metadata_sources, "");
321  m_io_ctx.poll();
322  ASSERT_TRUE(fut.is_ready())
323  << "future should be ready now since we faked a synchronous start with make_ready_future";
324  EXPECT_THROW(fut.get(), ocmif::DaqException)
325  << "future should have contained the ICD exception type";
326 }
327 
328 TEST_F(TestOcmDaqService, StopDaqShouldFailIfDaqDoesNotExist) {
329  // Setup
330  // Implementation might, or might not check if ID exist before issuing request to stop
331  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).Times(Between(0, 1)).WillRepeatedly(Return(false));
332  // Implementation might, or might not stop directly, leaving the id check to mgr
333  EXPECT_CALL(m_mgr_mock, StopDaqAsync("id"sv, daq::ErrorPolicy::Strict))
334  .Times(Between(0, 1))
335  .WillOnce(Return(ByMove(
336  boost::make_exceptional_future<daq::Status>(std::invalid_argument("no such id")))));
337 
338  // Run
339  auto reply_fut = m_daq_impl->StopDaq("id");
340  m_io_ctx.poll();
341  ASSERT_TRUE(reply_fut.is_ready());
342  EXPECT_THROW(reply_fut.get(), ocmif::DaqException);
343 }
344 
345 TEST_F(TestOcmDaqService, StopDaqShouldSucceedIfMgrOpSuceeds) {
346  // Setup
347  auto* fake_reply(new ocmif::DaqReplyFake); // Note: Raw pointer since MAL use unsafe APIs
348  daq::Status status("id", daq::State::Stopped, false, {}, {}, daq::Status::TimePoint());
349 
350  // Implementation might, or might not check if ID exist before issuing request to stop
351  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).Times(Between(0, 1)).WillRepeatedly(Return(true));
352  EXPECT_CALL(m_mgr_mock, StopDaqAsync("id"sv, daq::ErrorPolicy::Strict))
353  .WillOnce(Return(ByMove(boost::make_ready_future<daq::Status>(status))));
354  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "ocmif_DaqReply"))
355  .WillOnce(Return(fake_reply))
356  .RetiresOnSaturation();
357 
358  // Run
359  auto reply_fut = m_daq_impl->StopDaq("id");
360  m_io_ctx.poll();
361  ASSERT_TRUE(reply_fut.is_ready());
362  std::shared_ptr<ocmif::DaqReply> reply = reply_fut.get();
363  EXPECT_EQ(reply->getId(), "id");
364 }
365 
366 TEST_F(TestOcmDaqService, ForceStopDaqShouldSucceedIfMgrOpSuceeds) {
367  // Setup
368  auto* fake_reply(new ocmif::DaqReplyFake); // Note: Raw pointer since MAL use unsafe APIs
369  daq::Status status("id", daq::State::Stopped, false, {}, {}, daq::Status::TimePoint());
370 
371  // Implementation might, or might not check if ID exist before issuing request to stop
372  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).Times(Between(0, 1)).WillRepeatedly(Return(true));
373  EXPECT_CALL(m_mgr_mock, StopDaqAsync("id"sv, daq::ErrorPolicy::Tolerant))
374  .WillOnce(Return(ByMove(boost::make_ready_future<daq::Status>(status))));
375  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "ocmif_DaqReply"))
376  .WillOnce(Return(fake_reply))
377  .RetiresOnSaturation();
378 
379  // Run
380  auto reply_fut = m_daq_impl->ForceStopDaq("id");
381  m_io_ctx.poll();
382  ASSERT_TRUE(reply_fut.is_ready());
383  std::shared_ptr<ocmif::DaqReply> reply = reply_fut.get();
384  EXPECT_EQ(reply->getId(), "id");
385 }
386 
387 
388 TEST_F(TestOcmDaqService, AbortDaqShouldFailIfDaqDoesNotExist) {
389  // Setup
390  // Implementation might, or might not check if ID exist before issuing request to abort
391  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).Times(Between(0, 1)).WillRepeatedly(Return(false));
392  EXPECT_CALL(m_mgr_mock, AbortDaqAsync("id"sv, daq::ErrorPolicy::Strict))
393  .Times(Between(0, 1))
394  .WillOnce(Return(ByMove(
395  boost::make_exceptional_future<daq::Status>(std::invalid_argument("no such id")))));
396 
397  // Run
398  auto reply_fut = m_daq_impl->AbortDaq("id");
399  m_io_ctx.poll();
400  ASSERT_TRUE(reply_fut.is_ready());
401  EXPECT_THROW(reply_fut.get(), ocmif::DaqException);
402 }
403 
404 TEST_F(TestOcmDaqService, AbortDaqShouldSucceedIfMgrOpSuceeds) {
405  // Setup
406  auto* fake_reply(new ocmif::DaqReplyFake); // Note: Raw pointer since MAL use unsafe APIs
407 
408  auto reply_status = daq::Status("id");
409  reply_status.state = daq::State::Aborted;
410 
411  // Implementation might, or might not check if ID exist before issuing request to stop
412  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).Times(Between(0, 1)).WillRepeatedly(Return(true));
413  EXPECT_CALL(m_mgr_mock, AbortDaqAsync("id"sv, daq::ErrorPolicy::Strict))
414  .WillOnce(Return(ByMove(boost::make_ready_future<daq::Status>(reply_status))));
415  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "ocmif_DaqReply"))
416  .WillOnce(Return(fake_reply))
417  .RetiresOnSaturation();
418 
419  // Run
420  auto reply_fut = m_daq_impl->AbortDaq("id");
421  m_io_ctx.poll();
422  ASSERT_TRUE(reply_fut.is_ready());
423  std::shared_ptr<ocmif::DaqReply> reply = reply_fut.get();
424  EXPECT_EQ(reply->getId(), "id");
425 }
426 
427 TEST_F(TestOcmDaqService, ForceAbortDaqShouldUseTolerantPolicy) {
428  // Setup
429  auto* fake_reply(new ocmif::DaqReplyFake); // Note: Raw pointer since MAL use unsafe APIs
430  auto reply_status = daq::Status("id");
431  reply_status.state = daq::State::Aborted;
432 
433  // Implementation might, or might not check if ID exist before issuing request to stop
434  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).Times(Between(0, 1)).WillRepeatedly(Return(true));
435  EXPECT_CALL(m_mgr_mock, AbortDaqAsync("id"sv, daq::ErrorPolicy::Tolerant))
436  .WillOnce(Return(ByMove(boost::make_ready_future<daq::Status>(reply_status))));
437  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "ocmif_DaqReply"))
438  .WillOnce(Return(fake_reply))
439  .RetiresOnSaturation();
440 
441  // Run
442  auto reply_fut = m_daq_impl->ForceAbortDaq("id");
443  m_io_ctx.poll();
444  ASSERT_TRUE(reply_fut.is_ready());
445  std::shared_ptr<ocmif::DaqReply> reply = reply_fut.get();
446  EXPECT_EQ(reply->getId(), "id");
447 }
448 
449 TEST_F(TestOcmDaqService, UpdateKeywordsShouldSucceedIfMgrOpSucceeds) {
450  // Setup
451  std::string keywords = R"(
452  [
453  {
454  "type":"valueKeyword",
455  "name":"OBJECT",
456  "value":"OBJECT,SKY"
457  },
458  {
459  "type":"esoKeyword",
460  "name":"OBS TPLNO",
461  "value":2
462  }
463  ]
464  )";
465  // This is what `keywords` should contain when parsed.
466  daq::fits::KeywordVector parsed_keywords = {
467  daq::fits::ValueKeyword("OBJECT", "OBJECT,SKY"),
468  daq::fits::EsoKeyword("OBS TPLNO", static_cast<uint64_t>(2))};
469  auto* fake_reply(
470  new ocmif::DaqReplyFake); // Note: Raw pointer since MAL use unsafe APIs
471  // Implementation might, or might not check if ID exist before issuing request to stop
472  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).Times(Between(0, 1)).WillRepeatedly(Return(true));
473  EXPECT_CALL(m_mgr_mock, UpdateKeywords("id"sv, parsed_keywords));
474  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "ocmif_DaqReply"))
475  .WillOnce(Return(fake_reply))
476  .RetiresOnSaturation();
477 
478  // Run
479  auto reply_fut = m_daq_impl->UpdateKeywords("id", keywords);
480  m_io_ctx.poll();
481  ASSERT_TRUE(reply_fut.is_ready());
482  std::shared_ptr<ocmif::DaqReply> reply = reply_fut.get();
483  EXPECT_EQ(reply->getId(), "id");
484  EXPECT_EQ(reply->getError(), false);
485 }
486 
487 TEST_F(TestOcmDaqService, UpdateKeywordsFailsIfJsonParseFails) {
488  // Setup
489  std::string keywords_with_trailing_comma = R"(
490  [
491  {
492  "type":"valueKeyword",
493  "name":"OBJECT",
494  "value":"OBJECT,SKY"
495  },
496  {
497  "type":"esoKeyword",
498  "name":"OBS TPLNO",
499  "value":2
500  },
501  ]
502  )";
503  // Implementation might, or might not check if ID exist before issuing request to stop
504  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).Times(Between(0, 1)).WillRepeatedly(Return(true));
505  EXPECT_CALL(m_mgr_mock, UpdateKeywords("id"sv, _)).Times(0);
506 
507  // Run
508  auto reply_fut = m_daq_impl->UpdateKeywords("id", keywords_with_trailing_comma);
509  m_io_ctx.poll();
510  ASSERT_TRUE(reply_fut.is_ready());
511  EXPECT_THROW(reply_fut.get(), ocmif::DaqException);
512 }
513 
514 TEST_F(TestOcmDaqService, UpdateKeywordsFailsIfSchemaParsingFails) {
515  // Setup
516  std::string keywords_with_unknown_type = R"(
517  [
518  {
519  "type":"unknownKeywordHere",
520  "name":"OBJECT",
521  "value":"OBJECT,SKY"
522  }
523  ]
524  )";
525  // Implementation might, or might not check if ID exist before issuing request to stop
526  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).Times(Between(0, 1)).WillRepeatedly(Return(true));
527  EXPECT_CALL(m_mgr_mock, UpdateKeywords("id"sv, _)).Times(0);
528 
529  // Run
530  auto reply_fut = m_daq_impl->UpdateKeywords("id", keywords_with_unknown_type);
531  m_io_ctx.poll();
532  ASSERT_TRUE(reply_fut.is_ready());
533  EXPECT_THROW(reply_fut.get(), ocmif::DaqException);
534 }
535 
536 TEST_F(TestOcmDaqService, GetStatusFailsIfDaqDoesNotExist) {
537  // Setup
538  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).Times(Between(0, 1)).WillRepeatedly(Return(false));
539  EXPECT_CALL(m_mgr_mock, GetStatus("id"sv))
540  .Times(Between(0, 1))
541  .WillOnce(Throw(std::invalid_argument("no such id")));
542 
543  // Run
544  auto reply_fut = m_daq_impl->GetStatus("id");
545  EXPECT_FALSE(reply_fut.is_ready())
546  << "future cannot be ready since implementation should use provided executor to provide "
547  "thread safety";
548  m_io_ctx.poll();
549  ASSERT_TRUE(reply_fut.is_ready());
550  EXPECT_THROW(reply_fut.get(), ocmif::DaqException);
551 }
552 
553 TEST_F(TestOcmDaqService, GetStatusSuccedsIfMgrOpSucceeds) {
554  // Setup
555  daq::Status status("id", daq::State::Acquiring, false, {}, {}, daq::Status::TimePoint());
556  auto* fake_reply(
557  new ocmif::DaqStatusFake); // Note: Raw pointer since MAL use unsafe APIs
558 
559  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).Times(Between(0, 1)).WillRepeatedly(Return(true));
560  EXPECT_CALL(m_mgr_mock, GetStatus("id"sv)).Times(Between(0, 1)).WillOnce(Return(status));
561  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "ocmif_DaqStatus"))
562  .WillOnce(Return(fake_reply))
563  .RetiresOnSaturation();
564 
565  // Run
566  auto reply_fut = m_daq_impl->GetStatus("id");
567  EXPECT_FALSE(reply_fut.is_ready())
568  << "future cannot be ready since implementation should use provided executor to provide "
569  "thread safety";
570  m_io_ctx.poll();
571  ASSERT_TRUE(reply_fut.is_ready());
572  std::shared_ptr<ocmif::DaqStatus> reply = reply_fut.get();
573  EXPECT_EQ(reply->getId(), "id");
574  EXPECT_EQ(reply->getError(), false);
575  EXPECT_EQ(reply->getState(), ocmif::StateAcquiring);
576  EXPECT_EQ(reply->getSubState(), ocmif::Acquiring);
577 }
578 
579 TEST_F(TestOcmDaqService, GetActiveReturnsActive) {
580  // Setup
581  auto status1 = std::make_shared<daq::ObservableStatus>("completed");
582  auto status2 = std::make_shared<daq::ObservableStatus>("active1");
583  auto status3 = std::make_shared<daq::ObservableStatus>("active2");
584  auto status4 = std::make_shared<daq::ObservableStatus>("completed");
585  // Make DAQ completed
586  status1->SetState(daq::State::Aborted);
587  status4->SetState(daq::State::Aborted);
588 
589  auto daq1 = std::make_shared<DaqControllerFake>(status1);
590  auto daq2 = std::make_shared<DaqControllerFake>(status2);
591  auto daq3 = std::make_shared<DaqControllerFake>(status3);
592  auto daq4 = std::make_shared<DaqControllerFake>(status4);
593  std::vector<std::shared_ptr<daq::DaqController const>> daqs;
594  daqs.push_back(daq1);
595  daqs.push_back(daq2);
596  daqs.push_back(daq3);
597  daqs.push_back(daq4);
598 
599  auto* st1 = new ocmif::DaqStatusFake;
600  auto* st2 = new ocmif::DaqStatusFake;
601 
602  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "ocmif_DaqStatus"))
603  .WillOnce(Return(st1))
604  .WillOnce(Return(st2))
605  .RetiresOnSaturation();
606 
607  EXPECT_CALL(m_mgr_mock, GetDaqControllers()).WillOnce(Return(daqs));
608 
609  // Run
610  auto reply_fut = m_daq_impl->GetActiveList();
611  m_io_ctx.poll();
612  ASSERT_TRUE(reply_fut.is_ready());
613  auto reply = reply_fut.get();
614  EXPECT_EQ(reply.size(), 2u);
615  EXPECT_EQ(reply[0]->getId(), "active1");
616  EXPECT_EQ(reply[1]->getId(), "active2");
617 }
618 
619 TEST_F(TestOcmDaqService, AwaitDaqstateFailsWithInvalidArguments) {
620  // Setup
621 
622  // Run
623  {
624  // Invalid timeout
625  auto fut = m_daq_impl->AwaitDaqState("id", ocmif::StateAcquiring, ocmif::Acquiring, 0.0);
626  m_io_ctx.poll();
627  ASSERT_TRUE(fut.is_ready());
628  EXPECT_THROW(fut.get(), ocmif::DaqException);
629  }
630  m_io_ctx.reset();
631  {
632  // Invalid state combination
633  auto fut = m_daq_impl->AwaitDaqState("id", ocmif::StateMerging, ocmif::Acquiring, 1.0);
634  m_io_ctx.poll();
635  ASSERT_TRUE(fut.is_ready());
636  EXPECT_THROW(fut.get(), ocmif::DaqException);
637  }
638 }
639 
640 
641 TEST_F(TestOcmDaqService, AwaitDaqstateSucceds) {
642  // Setup
643  daq::Status status("id");
644  status.state = daq::State::Acquiring;
645 
646  boost::promise<daq::Result<daq::Status>> promise;
647  EXPECT_CALL(m_mgr_mock, AwaitDaqStateAsync("id"sv, daq::State::Acquiring, 1000ms))
648  .WillOnce(Return(ByMove(promise.get_future())));
649 
650  auto* reply = new ocmif::AwaitDaqReplyFake;
651 
652  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "ocmif_AwaitDaqReply"))
653  .WillOnce(Return(reply))
654  .RetiresOnSaturation();
655 
656  // Run
657  auto fut = m_daq_impl->AwaitDaqState("id", ocmif::StateAcquiring, ocmif::Acquiring, 1.0);
658  ASSERT_FALSE(fut.is_ready());
659  promise.set_value({false, status});
660  m_io_ctx.poll();
661  {
662  auto val = fut.get();
663  EXPECT_FALSE(val->getTimeout());
664  auto status = val->getStatus();
665  EXPECT_EQ(status->getId(), "id");
666  EXPECT_EQ(status->getError(), false);
667  EXPECT_EQ(status->getState(), ocmif::StateAcquiring);
668  EXPECT_EQ(status->getSubState(), ocmif::Acquiring);
669  }
670 }
671 
672 
674  // Setup
675  auto* mock_metadaq_client(new daq::MetaDaqAsyncMock);
676  EXPECT_CALL(m_mal_mock, getClientUnsafe(_, "metadaqif_MetaDaqAsync", _, _, _)).WillOnce(Return(mock_metadaq_client));
677  auto* mock_recif_client(new daq::RecCmdsAsyncMock);
678  EXPECT_CALL(m_mal_mock, getClientUnsafe(_, "recif_RecCmdsAsync", _, _, _)).WillOnce(Return(mock_recif_client));
679 
680  // This is the promise used to create result from daq::Manager
681  boost::promise<daq::State> mgr_promise;
682  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).WillOnce(Return(false));
683  EXPECT_CALL(m_mgr_mock, AddDaq(Pointee(Property(&daq::DaqController::GetId, "id"))));
684  EXPECT_CALL(m_mgr_mock, StartDaqAsync("id"sv))
685  .WillOnce(Return(ByMove(mgr_promise.get_future())));
686 
687  // Run
688  auto reply_fut = m_daq_impl->StartDaq("id", "prefix", m_prim_sources, m_metadata_sources, "");
689  // Abandon the service
690  m_daq_impl.reset();
691 
692  // Fulfull promise from daq::Manager:
693  mgr_promise.set_value(daq::State::Acquiring);
694 
695  // Trigger handlers
696  m_io_ctx.poll();
697  ASSERT_TRUE(reply_fut.is_ready());
698  EXPECT_THROW(reply_fut.get(), ocmif::DaqException);
699 }
700 
702  // Setup
703 
704  // This is the promise used to create result from daq::Manager
705  boost::promise<daq::Status> mgr_promise;
706 
707  // Implementation might, or might not check if ID exist before issuing request to stop
708  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).Times(Between(0, 1)).WillRepeatedly(Return(true));
709  EXPECT_CALL(m_mgr_mock, StopDaqAsync("id"sv, daq::ErrorPolicy::Strict))
710  .WillOnce(Return(ByMove(mgr_promise.get_future())));
711 
712  // Run
713  auto reply_fut = m_daq_impl->StopDaq("id");
714  // Abandon the service
715  m_daq_impl.reset();
716 
717  // Fulfull promise from daq::Manager:
718  mgr_promise.set_value(
719  daq::Status("id", daq::State::Stopped, true, daq::DpParts(), {}, daq::Status::TimePoint()));
720 
721  // Trigger handlers
722  m_io_ctx.poll();
723  ASSERT_TRUE(reply_fut.is_ready());
724  EXPECT_THROW(reply_fut.get(), ocmif::DaqException);
725 }
726 
728  // Setup
729 
730  // This is the promise used to create result from daq::Manager
731  boost::promise<daq::Status> mgr_promise;
732 
733  auto reply_status = daq::Status("id");
734  reply_status.state = daq::State::Aborted;
735 
736  // Implementation might, or might not check if ID exist before issuing request to stop
737  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv)).Times(Between(0, 1)).WillRepeatedly(Return(true));
738  EXPECT_CALL(m_mgr_mock, AbortDaqAsync("id"sv, daq::ErrorPolicy::Strict))
739  .WillOnce(Return(ByMove(mgr_promise.get_future())));
740 
741  // Run
742  auto reply_fut = m_daq_impl->AbortDaq("id");
743  // Abandon the service
744  m_daq_impl.reset();
745 
746  // Fulfull promise from daq::Manager:
747  mgr_promise.set_value(reply_status);
748 
749  // Trigger handlers
750  m_io_ctx.poll();
751  ASSERT_TRUE(reply_fut.is_ready());
752  EXPECT_THROW(reply_fut.get(), ocmif::DaqException);
753 }
TestOcmDaqService::m_daq_impl
std::shared_ptr< OcmDaqService > m_daq_impl
Definition: testOcmDaqService.cpp:66
ParseSourceUri
ParsedSource ParseSourceUri(std::string_view s)
Parse user provided string in the format "<name>@<rr-uri>".
Definition: ocmDaqService.cpp:123
ocmifMock.hpp
TestOcmDaqServiceAbandoned
Fixture for testing when OcmDaqService is abandoned (e.g.
Definition: testOcmDaqService.cpp:78
TestOcmDaqService::SetUp
void SetUp() override
Definition: testOcmDaqService.cpp:40
ParseSourceUris
std::vector< ParsedSource > ParseSourceUris(std::string_view s)
Parse user provided string in the format "<name>@<rr-uri>[ <name>@...]".
Definition: ocmDaqService.cpp:157
ocmif::DaqStatusFake
Definition: ocmifFake.hpp:39
metadaqifMock.hpp
Mockup of metadaqif classes.
daq::Status::state
State state
Definition: status.hpp:52
ocmif::AwaitDaqReplyFake
Definition: ocmifFake.hpp:99
ManagerMock
Definition: managerMock.hpp:9
TestOcmDaqService::TearDown
void TearDown() override
Definition: testOcmDaqService.cpp:60
daq::MetaDaqAsyncMock
Definition: metadaqifMock.hpp:17
daq::DaqSourceErrors
Exception thrown to carry reply errors.
Definition: error.hpp:84
malMock.hpp
TestOcmDaqService::m_daq_properties
std::string m_daq_properties
Definition: testOcmDaqService.cpp:69
ocmifFake.hpp
TEST
TEST(TestParseSingleSource, Successful)
Definition: testOcmDaqService.cpp:80
recifMock.hpp
Mockup of metadaqif classes.
TestOcmDaqService::m_mgr_mock
ManagerMock m_mgr_mock
Definition: testOcmDaqService.cpp:65
daq::fits::UpdateKeywords
void UpdateKeywords(KeywordVector &to, KeywordVector const &from)
Updates a with keywords from b.
Definition: keyword.cpp:120
MalMock
Definition: malMock.hpp:5
TestOcmDaqService::m_metadata_sources
std::string m_metadata_sources
Definition: testOcmDaqService.cpp:68
daq::fits::EsoKeyword
BasicKeyword< EsoKeywordTraits > EsoKeyword
ESO hiearchical keyword.
Definition: keyword.hpp:99
daq::Status::TimePoint
std::chrono::time_point< std::chrono::steady_clock > TimePoint
Definition: status.hpp:33
OcmDaqService
Implements the MAL interface ocmif::OcmDaq (async version).
Definition: ocmDaqService.hpp:88
ParsedSource::name
std::string name
Definition: ocmDaqService.hpp:34
daq::RecCmdsAsyncMock
Definition: recifMock.hpp:17
ocmDaqService.hpp
Declaration of OcmDaqService.
TestOcmDaqService::m_mal_mock
MalMock m_mal_mock
Definition: testOcmDaqService.cpp:64
managerMock.hpp
ocmif::DaqReplyFake
Definition: ocmifFake.hpp:7
TestOcmDaqService::m_prim_sources
std::string m_prim_sources
Definition: testOcmDaqService.cpp:67
daq::DaqController::GetId
virtual std::string const & GetId() const DAQ_NOEXCEPT=0
daq::fits::ValueKeyword
BasicKeyword< ValueKeywordTraits > ValueKeyword
Standard FITS value keyword.
Definition: keyword.hpp:92
daq::Status
Non observable status object that keeps stores status of data acquisition.
Definition: status.hpp:32
TestOcmDaqService::TestOcmDaqService
TestOcmDaqService()
Definition: testOcmDaqService.cpp:35
daq::fits::KeywordVector
std::vector< KeywordVariant > KeywordVector
Vector of keywords.
Definition: keyword.hpp:138
daq::DaqSourceError
Represents error in single source.
Definition: error.hpp:67
TestOcmDaqService
Fixture that sets up a OcmDaqService instance with following mockups:
Definition: testOcmDaqService.cpp:34
daq::State::NotStarted
@ NotStarted
Initial state of data acquisition.
daq::ErrorPolicy::Strict
@ Strict
Any error is considered fatal and may lead to the operation being aborted.
TestOcmDaqService::m_io_ctx
boost::asio::io_context m_io_ctx
Definition: testOcmDaqService.cpp:63
ParsedSource::rr_uri
std::string rr_uri
Definition: ocmDaqService.hpp:35
daq::DpParts
std::vector< DpPart > DpParts
Definition: dpPart.hpp:49
ParsedSource
Definition: ocmDaqService.hpp:25
ParseStartDaqProperties
daq::DaqProperties ParseStartDaqProperties(std::string const &json_properties)
Parse the JSON properties user provides with StartDaq.
Definition: ocmDaqService.cpp:76
TEST_F
TEST_F(TestOcmDaqService, StartDaqShouldTestIfIdAlreadyExistsAndReturnFailureIfItDoes)
Definition: testOcmDaqService.cpp:196
error.hpp
Contains error related declarations for DAQ.