Files
InstanceBrick--nightcap/tests/apk_provider_test.cc
T
LightZirconite 9da2c02836 Resolve APK providers through a replaceable chain
The coordinator constructed ApkPureProvider directly in three places and
rejected every other configuration with "native updater supports only the
direct APKPure provider". A single unreachable host therefore failed the
whole update with no way to try anything else, and the reported error named
neither the provider nor the host: on 2026-08-18 api.pureapk.com answered
503 to every request, including metadata for unrelated packages, and a
first launch could only say "Roblox update preflight failed".

Introduce an ApkProvider interface and a ProviderChain that consults
providers in order, returns the first answer, and reports every failure
with the provider that produced it when none answer. Each provider
downloads into its own directory, so a failed attempt cannot block the
next. Adding a source is now one line in BuildProviderChain instead of a
change spread across the coordinator.

The chain currently holds APKPure alone. The repository already ships a
pinned Uptodown provider as a build script, but it no longer works against
Uptodown's current pages, so wiring it in would add a second way to fail
rather than a fallback; that is reported separately.

Payload receipts now record which provider produced an archive, and
updates.source accepts auto alongside apk-pure so existing configuration
files keep working.
2026-08-19 10:41:11 +02:00

95 lines
3.0 KiB
C++

#include "update/apk_provider.h"
#include <gtest/gtest.h>
#include <filesystem>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
namespace mocktail::update {
namespace {
class StubProvider final : public ApkProvider {
public:
StubProvider(std::string name, std::string failure)
: name_(std::move(name)), failure_(std::move(failure)) {}
std::string_view name() const override { return name_; }
ProviderVersion CheckLatest() const override {
ProviderVersion version;
if (failure_.empty()) {
version.version_name = "2.727.1199";
version.version_code = 2628;
} else {
version.error = failure_;
}
return version;
}
ProviderDownloadResult DownloadExact(std::string_view,
const std::filesystem::path& directory,
int) const override {
ProviderDownloadResult result;
result.source = name_;
if (failure_.empty()) {
result.archives.push_back(directory / "candidate.apk");
} else {
result.error = failure_;
}
return result;
}
private:
std::string name_;
std::string failure_;
};
TEST(ProviderChainTest, FallsBackToTheNextProvider) {
ProviderChain chain;
chain.Add(std::make_unique<StubProvider>("apk-pure",
"api.pureapk.com request "
"returned status 503"));
chain.Add(std::make_unique<StubProvider>("pinned-mirror", ""));
const ProviderDownloadResult downloaded =
chain.DownloadExact("2.725.1142", "/tmp/mocktail-chain", -1);
ASSERT_TRUE(downloaded) << downloaded.error;
EXPECT_EQ(downloaded.source, "pinned-mirror");
ASSERT_EQ(downloaded.archives.size(), 1U);
// Each provider downloads into its own directory so a failed attempt cannot
// block the next one.
EXPECT_EQ(downloaded.archives.front(),
std::filesystem::path("/tmp/mocktail-chain/pinned-mirror/candidate.apk"));
}
TEST(ProviderChainTest, ReportsEveryProviderWhenAllFail) {
ProviderChain chain;
chain.Add(std::make_unique<StubProvider>("apk-pure", "returned status 503"));
chain.Add(std::make_unique<StubProvider>("pinned-mirror", "no pinned source"));
const ProviderDownloadResult downloaded =
chain.DownloadExact("2.734.917", "/tmp/mocktail-chain", -1);
EXPECT_FALSE(downloaded);
EXPECT_EQ(downloaded.error,
"apk-pure: returned status 503; pinned-mirror: no pinned source");
}
TEST(ProviderChainTest, PrefersTheFirstProviderThatAnswers) {
ProviderChain chain;
chain.Add(std::make_unique<StubProvider>("apk-pure", ""));
chain.Add(std::make_unique<StubProvider>("pinned-mirror", ""));
const ProviderVersion latest = chain.CheckLatest();
ASSERT_TRUE(latest) << latest.error;
EXPECT_EQ(latest.version_name, "2.727.1199");
}
TEST(ProviderChainTest, ReportsAnEmptyChain) {
const ProviderChain chain;
EXPECT_TRUE(chain.empty());
EXPECT_EQ(chain.CheckLatest().error, "no APK provider is configured");
}
} // namespace
} // namespace mocktail::update