diff options
-rw-r--r-- | platform/default/default_file_source.cpp | 36 | ||||
-rw-r--r-- | test/storage/default_file_source.cpp | 252 |
2 files changed, 277 insertions, 11 deletions
diff --git a/platform/default/default_file_source.cpp b/platform/default/default_file_source.cpp index afcb63e945..9d369210f8 100644 --- a/platform/default/default_file_source.cpp +++ b/platform/default/default_file_source.cpp @@ -82,21 +82,35 @@ public: } void request(AsyncRequest* req, Resource resource, Callback callback) { - auto offlineResponse = offlineDatabase.get(resource); - Resource revalidation = resource; - if (offlineResponse) { - revalidation.priorModified = offlineResponse->modified; - revalidation.priorExpires = offlineResponse->expires; - revalidation.priorEtag = offlineResponse->etag; - callback(*offlineResponse); + const bool hasPrior = resource.priorEtag || resource.priorModified || resource.priorExpires; + if (!hasPrior || resource.necessity == Resource::Optional) { + auto offlineResponse = offlineDatabase.get(resource); + + if (resource.necessity == Resource::Optional && !offlineResponse) { + // Ensure there's always a response that we can send, so the caller knows that + // there's no optional data available in the cache. + offlineResponse.emplace(); + offlineResponse->noContent = true; + offlineResponse->error = std::make_unique<Response::Error>( + Response::Error::Reason::NotFound, "Not found in offline database"); + } + + if (offlineResponse) { + revalidation.priorModified = offlineResponse->modified; + revalidation.priorExpires = offlineResponse->expires; + revalidation.priorEtag = offlineResponse->etag; + callback(*offlineResponse); + } } - tasks[req] = onlineFileSource.request(revalidation, [=] (Response onlineResponse) { - this->offlineDatabase.put(revalidation, onlineResponse); - callback(onlineResponse); - }); + if (resource.necessity == Resource::Required) { + tasks[req] = onlineFileSource.request(revalidation, [=] (Response onlineResponse) { + this->offlineDatabase.put(revalidation, onlineResponse); + callback(onlineResponse); + }); + } } void cancel(AsyncRequest* req) { diff --git a/test/storage/default_file_source.cpp b/test/storage/default_file_source.cpp index 09b10007e4..f4c23c4c7a 100644 --- a/test/storage/default_file_source.cpp +++ b/test/storage/default_file_source.cpp @@ -208,3 +208,255 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(HTTPIssue1369)) { loop.run(); } + +TEST(DefaultFileSource, OptionalNonExpired) { + util::RunLoop loop; + DefaultFileSource fs(":memory:", "."); + + const Resource optionalResource { Resource::Unknown, "http://127.0.0.1:3000/test", {}, Resource::Optional }; + + using namespace std::chrono_literals; + + Response response; + response.data = std::make_shared<std::string>("Cached value"); + response.expires = util::now() + 1h; + fs.put(optionalResource, response); + + std::unique_ptr<AsyncRequest> req; + req = fs.request(optionalResource, [&](Response res) { + req.reset(); + EXPECT_EQ(nullptr, res.error); + ASSERT_TRUE(res.data.get()); + EXPECT_EQ("Cached value", *res.data); + ASSERT_TRUE(bool(res.expires)); + EXPECT_EQ(*response.expires, *res.expires); + EXPECT_FALSE(bool(res.modified)); + EXPECT_FALSE(bool(res.etag)); + loop.stop(); + }); + + loop.run(); +} + +TEST(DefaultFileSource, OptionalExpired) { + util::RunLoop loop; + DefaultFileSource fs(":memory:", "."); + + const Resource optionalResource { Resource::Unknown, "http://127.0.0.1:3000/test", {}, Resource::Optional }; + + using namespace std::chrono_literals; + + Response response; + response.data = std::make_shared<std::string>("Cached value"); + response.expires = util::now() - 1h; + fs.put(optionalResource, response); + + std::unique_ptr<AsyncRequest> req; + req = fs.request(optionalResource, [&](Response res) { + req.reset(); + EXPECT_EQ(nullptr, res.error); + ASSERT_TRUE(res.data.get()); + EXPECT_EQ("Cached value", *res.data); + ASSERT_TRUE(bool(res.expires)); + EXPECT_EQ(*response.expires, *res.expires); + EXPECT_FALSE(bool(res.modified)); + EXPECT_FALSE(bool(res.etag)); + loop.stop(); + }); + + loop.run(); +} + +TEST(DefaultFileSource, OptionalNotFound) { + util::RunLoop loop; + DefaultFileSource fs(":memory:", "."); + + const Resource optionalResource { Resource::Unknown, "http://127.0.0.1:3000/test", {}, Resource::Optional }; + + using namespace std::chrono_literals; + + std::unique_ptr<AsyncRequest> req; + req = fs.request(optionalResource, [&](Response res) { + req.reset(); + ASSERT_TRUE(res.error.get()); + EXPECT_EQ(Response::Error::Reason::NotFound, res.error->reason); + EXPECT_EQ("Not found in offline database", res.error->message); + EXPECT_FALSE(res.data); + EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(bool(res.modified)); + EXPECT_FALSE(bool(res.etag)); + loop.stop(); + }); + + loop.run(); +} + +// Test that we can make a request with etag data that doesn't first try to load +// from cache like a regular request +TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheRefreshEtagNotModified)) { + util::RunLoop loop; + DefaultFileSource fs(":memory:", "."); + + Resource resource { Resource::Unknown, "http://127.0.0.1:3000/revalidate-same" }; + resource.priorEtag.emplace("snowfall"); + + using namespace std::chrono_literals; + + // Put a fake value into the cache to make sure we're not retrieving anything from the cache. + Response response; + response.data = std::make_shared<std::string>("Cached value"); + response.expires = util::now() + 1h; + fs.put(resource, response); + + std::unique_ptr<AsyncRequest> req; + req = fs.request(resource, [&](Response res) { + req.reset(); + EXPECT_EQ(nullptr, res.error); + EXPECT_TRUE(res.notModified); + EXPECT_FALSE(res.data.get()); + ASSERT_TRUE(bool(res.expires)); + EXPECT_LT(util::now(), *res.expires); + EXPECT_FALSE(bool(res.modified)); + ASSERT_TRUE(bool(res.etag)); + EXPECT_EQ("snowfall", *res.etag); + loop.stop(); + }); + + loop.run(); +} + +// Test that we can make a request with etag data that doesn't first try to load +// from cache like a regular request +TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheRefreshEtagModified)) { + util::RunLoop loop; + DefaultFileSource fs(":memory:", "."); + + Resource resource { Resource::Unknown, "http://127.0.0.1:3000/revalidate-same" }; + resource.priorEtag.emplace("sunshine"); + + using namespace std::chrono_literals; + + // Put a fake value into the cache to make sure we're not retrieving anything from the cache. + Response response; + response.data = std::make_shared<std::string>("Cached value"); + response.expires = util::now() + 1h; + fs.put(resource, response); + + std::unique_ptr<AsyncRequest> req; + req = fs.request(resource, [&](Response res) { + req.reset(); + EXPECT_EQ(nullptr, res.error); + EXPECT_FALSE(res.notModified); + ASSERT_TRUE(res.data.get()); + EXPECT_EQ("Response", *res.data); + EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(bool(res.modified)); + ASSERT_TRUE(bool(res.etag)); + EXPECT_EQ("snowfall", *res.etag); + loop.stop(); + }); + + loop.run(); +} + +// Test that we can make a request that doesn't first try to load +// from cache like a regular request. +TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheFull)) { + util::RunLoop loop; + DefaultFileSource fs(":memory:", "."); + + Resource resource { Resource::Unknown, "http://127.0.0.1:3000/revalidate-same" }; + // Setting any prior field results in skipping the cache. + resource.priorExpires.emplace(Seconds(0)); + + using namespace std::chrono_literals; + + // Put a fake value into the cache to make sure we're not retrieving anything from the cache. + Response response; + response.data = std::make_shared<std::string>("Cached value"); + response.expires = util::now() + 1h; + fs.put(resource, response); + + std::unique_ptr<AsyncRequest> req; + req = fs.request(resource, [&](Response res) { + req.reset(); + EXPECT_EQ(nullptr, res.error); + EXPECT_FALSE(res.notModified); + ASSERT_TRUE(res.data.get()); + EXPECT_EQ("Response", *res.data); + EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(bool(res.modified)); + ASSERT_TRUE(bool(res.etag)); + EXPECT_EQ("snowfall", *res.etag); + loop.stop(); + }); + + loop.run(); +} + +// Test that we can make a request with a Modified field that doesn't first try to load +// from cache like a regular request +TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheRefreshModifiedNotModified)) { + util::RunLoop loop; + DefaultFileSource fs(":memory:", "."); + + Resource resource { Resource::Unknown, "http://127.0.0.1:3000/revalidate-modified" }; + resource.priorModified.emplace(Seconds(1420070400)); // January 1, 2015 + + using namespace std::chrono_literals; + + // Put a fake value into the cache to make sure we're not retrieving anything from the cache. + Response response; + response.data = std::make_shared<std::string>("Cached value"); + response.expires = util::now() + 1h; + fs.put(resource, response); + + std::unique_ptr<AsyncRequest> req; + req = fs.request(resource, [&](Response res) { + req.reset(); + EXPECT_EQ(nullptr, res.error); + EXPECT_TRUE(res.notModified); + EXPECT_FALSE(res.data.get()); + ASSERT_TRUE(bool(res.expires)); + EXPECT_LT(util::now(), *res.expires); + ASSERT_TRUE(bool(res.modified)); + EXPECT_EQ(Timestamp{ Seconds(1420070400) }, *res.modified); + EXPECT_FALSE(bool(res.etag)); + loop.stop(); + }); + + loop.run(); +} + +// Test that we can make a request with a Modified field that doesn't first try to load +// from cache like a regular request +TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheRefreshModifiedModified)) { + util::RunLoop loop; + DefaultFileSource fs(":memory:", "."); + + Resource resource { Resource::Unknown, "http://127.0.0.1:3000/revalidate-modified" }; + resource.priorModified.emplace(Seconds(1417392000)); // December 1, 2014 + + using namespace std::chrono_literals; + + // Put a fake value into the cache to make sure we're not retrieving anything from the cache. + Response response; + response.data = std::make_shared<std::string>("Cached value"); + response.expires = util::now() + 1h; + fs.put(resource, response); + + std::unique_ptr<AsyncRequest> req; + req = fs.request(resource, [&](Response res) { + req.reset(); + EXPECT_EQ(nullptr, res.error); + EXPECT_FALSE(res.notModified); + ASSERT_TRUE(res.data.get()); + EXPECT_EQ("Response", *res.data); + EXPECT_FALSE(bool(res.expires)); + EXPECT_EQ(Timestamp{ Seconds(1420070400) }, *res.modified); + EXPECT_FALSE(res.etag); + loop.stop(); + }); + + loop.run(); +} |