implement Milestone 14: location interface with coarse/fine precision and rate limiting
This commit is contained in:
@@ -27,6 +27,7 @@ add_library(mosis-sandbox STATIC
|
||||
../src/main/cpp/sandbox/camera_interface.cpp
|
||||
../src/main/cpp/sandbox/microphone_interface.cpp
|
||||
../src/main/cpp/sandbox/audio_output.cpp
|
||||
../src/main/cpp/sandbox/location_interface.cpp
|
||||
)
|
||||
target_include_directories(mosis-sandbox PUBLIC
|
||||
../src/main/cpp/sandbox
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "camera_interface.h"
|
||||
#include "microphone_interface.h"
|
||||
#include "audio_output.h"
|
||||
#include "location_interface.h"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
@@ -2323,6 +2324,201 @@ bool Test_AudioLuaIntegration(std::string& error_msg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// MILESTONE 14: Location
|
||||
//=============================================================================
|
||||
|
||||
bool Test_LocationRequiresPermission(std::string& error_msg) {
|
||||
mosis::LocationInterface location("test.app");
|
||||
// No permissions granted
|
||||
|
||||
mosis::LocationOptions options;
|
||||
std::string err;
|
||||
bool started = location.GetCurrentPosition(
|
||||
options,
|
||||
[](const mosis::Position&) {},
|
||||
[](mosis::LocationError, const std::string&) {},
|
||||
err
|
||||
);
|
||||
|
||||
EXPECT_TRUE(!started);
|
||||
EXPECT_TRUE(err.find("permission") != std::string::npos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_LocationCoarseReducesPrecision(std::string& error_msg) {
|
||||
mosis::LocationInterface location("test.app");
|
||||
location.SetCoarsePermission(true); // Only coarse, not fine
|
||||
|
||||
// Set mock position with high precision
|
||||
mosis::Position precise;
|
||||
precise.latitude = 37.7749295;
|
||||
precise.longitude = -122.4194155;
|
||||
precise.accuracy = 5.0;
|
||||
precise.altitude = 100.0;
|
||||
location.SetMockPosition(precise);
|
||||
|
||||
bool got_position = false;
|
||||
mosis::Position received;
|
||||
|
||||
mosis::LocationOptions options;
|
||||
std::string err;
|
||||
location.GetCurrentPosition(
|
||||
options,
|
||||
[&](const mosis::Position& pos) {
|
||||
got_position = true;
|
||||
received = pos;
|
||||
},
|
||||
[](mosis::LocationError, const std::string&) {},
|
||||
err
|
||||
);
|
||||
|
||||
EXPECT_TRUE(got_position);
|
||||
// Should be rounded to ~0.01 degrees
|
||||
EXPECT_TRUE(std::abs(received.latitude - 37.77) < 0.001);
|
||||
EXPECT_TRUE(std::abs(received.longitude - (-122.42)) < 0.001);
|
||||
// Accuracy should be at least 1000m
|
||||
EXPECT_TRUE(received.accuracy >= 1000.0);
|
||||
// Fine details should be zeroed
|
||||
EXPECT_TRUE(received.altitude == 0.0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_LocationRateLimits(std::string& error_msg) {
|
||||
mosis::LocationInterface location("test.app");
|
||||
location.SetCoarsePermission(true);
|
||||
|
||||
mosis::Position pos;
|
||||
pos.latitude = 37.77;
|
||||
pos.longitude = -122.42;
|
||||
location.SetMockPosition(pos);
|
||||
|
||||
mosis::LocationOptions options;
|
||||
|
||||
// First request should succeed
|
||||
std::string err;
|
||||
bool ok1 = location.GetCurrentPosition(
|
||||
options,
|
||||
[](const mosis::Position&) {},
|
||||
[](mosis::LocationError, const std::string&) {},
|
||||
err
|
||||
);
|
||||
EXPECT_TRUE(ok1);
|
||||
|
||||
// Immediate second request should be rate limited
|
||||
bool ok2 = location.GetCurrentPosition(
|
||||
options,
|
||||
[](const mosis::Position&) {},
|
||||
[](mosis::LocationError, const std::string&) {},
|
||||
err
|
||||
);
|
||||
EXPECT_TRUE(!ok2);
|
||||
EXPECT_TRUE(err.find("rate") != std::string::npos ||
|
||||
err.find("limit") != std::string::npos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_LocationWatchPosition(std::string& error_msg) {
|
||||
mosis::LocationInterface location("test.app");
|
||||
location.SetFinePermission(true);
|
||||
|
||||
std::string err;
|
||||
auto watch = location.WatchPosition({}, err);
|
||||
|
||||
EXPECT_TRUE(watch != nullptr);
|
||||
EXPECT_TRUE(watch->IsActive());
|
||||
EXPECT_TRUE(location.GetActiveWatchCount() == 1);
|
||||
|
||||
watch->Stop();
|
||||
EXPECT_TRUE(!watch->IsActive());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_LocationWatchLimit(std::string& error_msg) {
|
||||
mosis::LocationInterface location("test.app");
|
||||
location.SetCoarsePermission(true);
|
||||
|
||||
std::vector<std::shared_ptr<mosis::LocationWatch>> watches;
|
||||
std::string err;
|
||||
|
||||
// Create MAX_WATCHES (5) watches
|
||||
for (int i = 0; i < 5; i++) {
|
||||
auto watch = location.WatchPosition({}, err);
|
||||
EXPECT_TRUE(watch != nullptr);
|
||||
watches.push_back(watch);
|
||||
}
|
||||
|
||||
// 6th should fail
|
||||
auto extra = location.WatchPosition({}, err);
|
||||
EXPECT_TRUE(extra == nullptr);
|
||||
EXPECT_TRUE(err.find("limit") != std::string::npos ||
|
||||
err.find("maximum") != std::string::npos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_LocationCleansUpOnShutdown(std::string& error_msg) {
|
||||
mosis::LocationInterface location("test.app");
|
||||
location.SetCoarsePermission(true);
|
||||
|
||||
std::string err;
|
||||
auto watch1 = location.WatchPosition({}, err);
|
||||
auto watch2 = location.WatchPosition({}, err);
|
||||
|
||||
EXPECT_TRUE(location.GetActiveWatchCount() == 2);
|
||||
|
||||
location.Shutdown();
|
||||
|
||||
EXPECT_TRUE(location.GetActiveWatchCount() == 0);
|
||||
EXPECT_TRUE(!watch1->IsActive());
|
||||
EXPECT_TRUE(!watch2->IsActive());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_LocationLuaIntegration(std::string& error_msg) {
|
||||
SandboxContext ctx = TestContext();
|
||||
LuaSandbox sandbox(ctx);
|
||||
|
||||
mosis::LocationInterface location("test.app");
|
||||
mosis::RegisterLocationAPI(sandbox.GetState(), &location);
|
||||
|
||||
std::string script = R"lua(
|
||||
-- Test that location global exists
|
||||
if not location then
|
||||
error("location global not found")
|
||||
end
|
||||
if not location.getCurrentPosition then
|
||||
error("location.getCurrentPosition not found")
|
||||
end
|
||||
if not location.watchPosition then
|
||||
error("location.watchPosition not found")
|
||||
end
|
||||
if not location.clearWatch then
|
||||
error("location.clearWatch not found")
|
||||
end
|
||||
if not location.getWatchCount then
|
||||
error("location.getWatchCount not found")
|
||||
end
|
||||
|
||||
-- Watch count should be 0 initially
|
||||
if location.getWatchCount() ~= 0 then
|
||||
error("should have no active watches initially")
|
||||
end
|
||||
)lua";
|
||||
|
||||
bool ok = sandbox.LoadString(script, "location_test");
|
||||
if (!ok) {
|
||||
error_msg = "Lua test failed: " + sandbox.GetLastError();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// MAIN
|
||||
//=============================================================================
|
||||
@@ -2488,6 +2684,15 @@ int main(int argc, char* argv[]) {
|
||||
harness.AddTest("AudioStopsOnShutdown", Test_AudioStopsOnShutdown);
|
||||
harness.AddTest("AudioLuaIntegration", Test_AudioLuaIntegration);
|
||||
|
||||
// Milestone 14: Location
|
||||
harness.AddTest("LocationRequiresPermission", Test_LocationRequiresPermission);
|
||||
harness.AddTest("LocationCoarseReducesPrecision", Test_LocationCoarseReducesPrecision);
|
||||
harness.AddTest("LocationRateLimits", Test_LocationRateLimits);
|
||||
harness.AddTest("LocationWatchPosition", Test_LocationWatchPosition);
|
||||
harness.AddTest("LocationWatchLimit", Test_LocationWatchLimit);
|
||||
harness.AddTest("LocationCleansUpOnShutdown", Test_LocationCleansUpOnShutdown);
|
||||
harness.AddTest("LocationLuaIntegration", Test_LocationLuaIntegration);
|
||||
|
||||
// Run tests
|
||||
auto results = harness.Run(filter);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user