// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "call/CallEvents.hpp" #include #include #include namespace mocks { class Sentinel : public sys::CpuSentinel { public: Sentinel() : sys::CpuSentinel{"test", nullptr, nullptr} {} void HoldMinimumFrequency(bsp::CpuFrequencyMHz frequencyToHold) override {} void ReleaseMinimumFrequency() override {} }; auto audio() { fakeit::Mock audio; fakeit::When(Method(audio, play)).AlwaysReturn(); fakeit::When(Method(audio, stop)).AlwaysReturn(); fakeit::When(Method(audio, muteCall)).AlwaysReturn(); fakeit::When(Method(audio, unmuteCall)).AlwaysReturn(); fakeit::When(Method(audio, setLoudspeakerOn)).AlwaysReturn(); fakeit::When(Method(audio, setLoudspeakerOff)).AlwaysReturn(); fakeit::When(Method(audio, routingStart)).AlwaysReturn(); return audio; } auto gui() { fakeit::Mock gui; fakeit::When(Method(gui, notifyRING)).AlwaysReturn(); fakeit::When(Method(gui, notifyCLIP)).AlwaysReturn(); fakeit::When(Method(gui, notifyCallStarted)).AlwaysReturn(); fakeit::When(Method(gui, notifyCallEnded)).AlwaysReturn(); fakeit::When(Method(gui, notifyCallActive)).AlwaysReturn(); fakeit::When(Method(gui, notifyCallDurationUpdate)).AlwaysReturn(); return gui; } auto multicast() { fakeit::Mock multicast; fakeit::When(Method(multicast, notifyIncommingCall)).AlwaysReturn(); fakeit::When(Method(multicast, notifyIdentifiedCall)).AlwaysReturn(); fakeit::When(Method(multicast, notifyCallActive)).AlwaysReturn(); fakeit::When(Method(multicast, notifyCallAborted)).AlwaysReturn(); return multicast; }; auto db() { fakeit::Mock db; fakeit::When(Method(db, incrementNotAnsweredCallsNotification)).AlwaysReturn(); fakeit::When(Method(db, startCall)).AlwaysReturn(); fakeit::When(Method(db, endCall)).AlwaysReturn(); fakeit::When(Method(db, isNumberInFavourites)).AlwaysReturn(true); return db; } auto timer() { fakeit::Mock timer; fakeit::When(Method(timer, start)).AlwaysReturn(); fakeit::When(Method(timer, stop)).AlwaysReturn(); fakeit::When(Method(timer, duration)).AlwaysReturn(0); return timer; } auto timerRing() { fakeit::Mock timer; fakeit::When(Method(timer, start)).AlwaysReturn(); return timer; } auto api(bool dnd = false, bool tethering = false) { fakeit::Mock api; fakeit::When(Method(api, answerIncommingCall)).AlwaysReturn(true); fakeit::When(Method(api, hangupCall)).AlwaysReturn(true); fakeit::When(Method(api, rejectCall)).AlwaysReturn(true); fakeit::When(Method(api, areCallsFromFavouritesEnabled)).AlwaysReturn(true); fakeit::When(Method(api, getMode)) .AlwaysReturn(dnd ? sys::phone_modes::PhoneMode::DoNotDisturb : sys::phone_modes::PhoneMode::Connected); fakeit::When(Method(api, getTethering)) .AlwaysReturn(tethering ? sys::phone_modes::Tethering::On : sys::phone_modes::Tethering::Off); return api; } template auto mock_to_shared(T *val) { auto t = std::shared_ptr(val, [](...) {}); return t; } struct DIWrapper { decltype(mocks::audio()) audio = mocks::audio(); decltype(mocks::multicast()) multicast = mocks::multicast(); decltype(mocks::gui()) gui = mocks::gui(); decltype(mocks::db()) db = mocks::db(); decltype(mocks::timer()) timer = mocks::timer(); decltype(mocks::timerRing()) timerRing = mocks::timerRing(); decltype(mocks::api()) api; call::Dependencies di{}; std::shared_ptr sentinel = std::make_shared(); DIWrapper(bool dnd = false, bool tethering = false) : api(mocks::api(dnd, tethering)) { auto audio_p = mock_to_shared(&audio.get()); auto multicast_p = mock_to_shared(&multicast.get()); auto gui_p = mock_to_shared(&gui.get()); auto db_p = mock_to_shared(&db.get()); auto timer_p = mock_to_shared(&timer.get()); auto timer_ring_p = mock_to_shared(&timerRing.get()); auto api_p = mock_to_shared(&api.get()); di = call::Dependencies{std::move(audio_p), std::move(multicast_p), std::move(gui_p), std::move(db_p), std::move(timer_p), std::move(timer_ring_p), std::move(api_p), sentinel}; } auto get() { return di; } }; } // namespace mocks // ---------------------------------- // INCOMMING CALL TESTS - mode normal // ---------------------------------- TEST_CASE("base positive call flow, answered, end from caller") { auto number = utils::PhoneNumber("+48700800900"); auto di = mocks::DIWrapper(); auto machine = std::make_unique(di.get()); // 0 rings fakeit::Verify(Method(di.audio, play)).Exactly(0); // 1st ring REQUIRE(machine->machine.process_event(call::event::RING{})); fakeit::Verify(Method(di.audio, play)).Exactly(0); fakeit::Verify(Method(di.timerRing, start)).Exactly(1); // 2nd ring - ignored REQUIRE(not machine->machine.process_event(call::event::RING{})); fakeit::Verify(Method(di.timerRing, start)).Exactly(1); REQUIRE(machine->machine.process_event(call::event::RingTimeout{})); // we have to wait for RingTimeout first fakeit::Verify(Method(di.audio, play)).Exactly(1); REQUIRE(machine->machine.process_event(call::event::CLIP{number.getView()})); REQUIRE(machine->call.record.phoneNumber == number.getView()); fakeit::Verify(Method(di.gui, notifyCLIP)).Exactly(1); // answer the call from the pure REQUIRE(machine->machine.process_event(call::event::Answer{})); fakeit::Verify(Method(di.audio, routingStart)).Exactly(1); fakeit::Verify(Method(di.gui, notifyCallActive)).Exactly(1); REQUIRE(machine->machine.process_event(call::event::OngoingTimer{})); REQUIRE(machine->machine.process_event(call::event::Ended{})); } TEST_CASE("CLIP before ring, answered, end from caller") { auto number = utils::PhoneNumber("+48700800900"); auto di = mocks::DIWrapper(); auto machine = std::make_unique(di.get()); REQUIRE(machine->machine.process_event(call::event::CLIP{number.getView()})); REQUIRE(machine->call.record.phoneNumber == number.getView()); fakeit::Verify(Method(di.audio, play)).Exactly(1); fakeit::Verify(Method(di.gui, notifyCLIP)).Exactly(1); REQUIRE(machine->machine.process_event(call::event::Answer{})); fakeit::Verify(Method(di.audio, routingStart)).Exactly(1); fakeit::Verify(Method(di.gui, notifyCallActive)).Exactly(1); REQUIRE(machine->machine.process_event(call::event::OngoingTimer{})); CalllogRecord record_when_called; fakeit::When(Method(di.db, endCall)).AlwaysDo([&record_when_called](const CalllogRecord &rec) { record_when_called = rec; }); REQUIRE(machine->machine.process_event(call::event::Ended{})); REQUIRE(record_when_called.type == CallType::CT_INCOMING); REQUIRE(record_when_called.phoneNumber == number.getView()); } /// no clip is when: caller id is unknown TEST_CASE("no CLIP at all, anonymus call answered, end from caller") { auto di = mocks::DIWrapper(); auto machine = std::make_unique(di.get()); fakeit::Verify(Method(di.audio, play)).Exactly(0); REQUIRE(machine->machine.process_event(call::event::RING{})); REQUIRE(machine->machine.process_event(call::event::RingTimeout{})); fakeit::Verify(Method(di.audio, play)).Exactly(1); fakeit::Verify(Method(di.gui, notifyRING)).Exactly(1); REQUIRE(machine->machine.process_event(call::event::Answer{})); fakeit::Verify(Method(di.audio, routingStart)).Exactly(1); fakeit::Verify(Method(di.gui, notifyCallActive)).Exactly(1); REQUIRE(machine->machine.process_event(call::event::OngoingTimer{})); // we cant use fakeit matching - as it has const references, not copies of object // see: https://github.com/eranpeer/FakeIt/issues/31 // therefore we store value to be checked and then check it CalllogRecord record_when_called; fakeit::When(Method(di.db, endCall)).AlwaysDo([&record_when_called](const CalllogRecord &rec) { record_when_called = rec; }); REQUIRE(machine->machine.process_event(call::event::Ended{})); // no CLIP: there was no phone number set // - therefore we call db save with empty phone nr // - call type set is answered REQUIRE(record_when_called.type == CallType::CT_INCOMING); REQUIRE(record_when_called.phoneNumber == utils::PhoneNumber().getView()); } TEST_CASE("reject on PURE after RING") { auto di = mocks::DIWrapper(); auto machine = std::make_unique(di.get()); REQUIRE(machine->machine.process_event(call::event::RING{})); REQUIRE(machine->machine.process_event(call::event::RingTimeout{})); CalllogRecord record_when_called; fakeit::When(Method(di.db, endCall)).AlwaysDo([&record_when_called](const CalllogRecord &rec) { record_when_called = rec; }); REQUIRE(machine->machine.process_event(call::event::Reject{})); fakeit::Verify(Method(di.audio, stop)).Exactly(1); // UI was notified to stop "calling" display fakeit::Verify(Method(di.gui, notifyCallEnded)).Exactly(1); REQUIRE(record_when_called.type == CallType::CT_REJECTED); } TEST_CASE("reject on PURE after CLIP") { auto number = utils::PhoneNumber("+48700800900"); auto di = mocks::DIWrapper(); auto machine = std::make_unique(di.get()); REQUIRE(machine->machine.process_event(call::event::RING{})); REQUIRE(machine->machine.process_event(call::event::CLIP{number.getView()})); fakeit::Verify(Method(di.audio, play)).Exactly(1); CalllogRecord record_when_called; fakeit::When(Method(di.db, endCall)).AlwaysDo([&record_when_called](const CalllogRecord &rec) { record_when_called = rec; }); REQUIRE(machine->machine.process_event(call::event::Reject{})); fakeit::Verify(Method(di.audio, stop)).Exactly(1); REQUIRE(record_when_called.type == CallType::CT_REJECTED); } TEST_CASE("call missed on PURE after CLIP") { auto number = utils::PhoneNumber("+48700800900"); auto di = mocks::DIWrapper(); auto machine = std::make_unique(di.get()); REQUIRE(machine->machine.process_event(call::event::RING{})); REQUIRE(machine->machine.process_event(call::event::CLIP{number.getView()})); CalllogRecord record_when_called; fakeit::When(Method(di.db, endCall)).AlwaysDo([&record_when_called](const CalllogRecord &rec) { record_when_called = rec; }); REQUIRE(machine->machine.process_event(call::event::Ended{})); REQUIRE(record_when_called.type == CallType::CT_MISSED); } // --------------------------------- // INCOMMING CALL TESTS - mode dnd // --------------------------------- TEST_CASE("call incoming - dnd - not favourite") { auto number = utils::PhoneNumber("+48700800900"); auto di = mocks::DIWrapper(true); auto machine = std::make_unique(di.get()); fakeit::When(Method(di.db, isNumberInFavourites)).AlwaysReturn(false); REQUIRE(not machine->machine.process_event(call::event::RING{})); REQUIRE(machine->machine.process_event(call::event::CLIP{number.getView()})); fakeit::Verify(Method(di.audio, play)).Exactly(0); fakeit::Verify(Method(di.api, rejectCall)).Exactly(1); } TEST_CASE("call incoming - dnd - in favourite") { auto number = utils::PhoneNumber("+48700800900"); auto di = mocks::DIWrapper(true); auto machine = std::make_unique(di.get()); fakeit::When(Method(di.db, isNumberInFavourites)).AlwaysReturn(true); REQUIRE(not machine->machine.process_event(call::event::RING{})); REQUIRE(machine->machine.process_event(call::event::CLIP{number.getView()})); fakeit::Verify(Method(di.audio, play)).Exactly(1); fakeit::Verify(Method(di.api, rejectCall)).Exactly(0); using namespace boost::sml; REQUIRE(machine->machine.is("HaveId"_s)); } TEST_CASE("call incomming - proper date") { auto di = mocks::DIWrapper(); auto machine = std::make_unique(di.get()); REQUIRE(machine->machine.process_event(call::event::RING{})); auto t = std::time(nullptr); auto date = *std::localtime(&t); auto saved = *std::localtime(&machine->call.record.date); REQUIRE(date.tm_year == saved.tm_year); REQUIRE(date.tm_mday == saved.tm_mday); REQUIRE(date.tm_hour == saved.tm_hour); } // --------------------------------- // OUTGOING CALL TESTS - mode normal // --------------------------------- TEST_CASE("call outgoing - answered") { auto number = utils::PhoneNumber("+48700800900"); auto di = mocks::DIWrapper(); auto machine = std::make_unique(di.get()); REQUIRE(machine->machine.process_event(call::event::StartCall{CallType::CT_OUTGOING, number.getView()})); REQUIRE(machine->machine.process_event(call::event::Answer{})); fakeit::Verify(Method(di.audio, play)).Exactly(0); REQUIRE(machine->machine.process_event(call::event::Ended{})); fakeit::Verify(Method(di.audio, stop)).Exactly(1); } TEST_CASE("call outgoing - ended, before answered") { auto number = utils::PhoneNumber("+48700800900"); auto di = mocks::DIWrapper(); auto machine = std::make_unique(di.get()); REQUIRE(machine->machine.process_event(call::event::StartCall{CallType::CT_OUTGOING, number.getView()})); fakeit::Verify(Method(di.audio, routingStart)).Exactly(1); fakeit::Verify(Method(di.audio, play)).Exactly(0); REQUIRE(machine->machine.process_event(call::event::Ended{})); fakeit::Verify(Method(di.audio, stop)).Exactly(1); } // --------------------------------- // TEST EXCEPTION ABORTION HANDLER // --------------------------------- TEST_CASE("test call exception abort") { auto number = utils::PhoneNumber("+48700800900"); auto di = mocks::DIWrapper(); auto machine = std::make_unique(di.get()); REQUIRE(machine->machine.process_event(call::event::StartCall{CallType::CT_OUTGOING, number.getView()})); REQUIRE(machine->machine.process_event(call::event::Answer{})); fakeit::When(Method(di.audio, stop)).AlwaysDo([] { throw std::runtime_error("lol"); return true; }); REQUIRE(machine->machine.process_event(call::event::Ended{})); fakeit::Verify(Method(di.multicast, notifyCallAborted)).Exactly(1); } // -------------- // TEST TETHERING // -------------- TEST_CASE("test call in tethering") { auto number = utils::PhoneNumber("+48700800900"); auto di = mocks::DIWrapper(false, true); auto machine = std::make_unique(di.get()); REQUIRE(not machine->machine.process_event(call::event::RING{})); REQUIRE(machine->machine.process_event(call::event::CLIP{number.getView()})); fakeit::Verify(Method(di.audio, play)).Exactly(0); fakeit::Verify(Method(di.api, rejectCall)).Exactly(1); }