#include #include #include #include "update_check.h" using namespace roro::release; void setUp() {} void tearDown() {} static std::string file(const char* name) { std::string path = std::string("test/test_release/fixtures/") + name; FILE* f = std::fopen(path.c_str(), "rb"); TEST_ASSERT_NOT_NULL_MESSAGE(f, path.c_str()); std::string s; char buf[4096]; size_t n; while ((n = std::fread(buf, 1, sizeof buf, f)) > 0) s.append(buf, n); std::fclose(f); return s; } static std::vector read(const std::string& json, size_t piece, size_t max = 10, bool* complete = nullptr) { ReleaseReader r(max); for (size_t at = 0; at < json.size(); at += piece) r.feed(json.data() + at, std::min(piece, json.size() - at)); r.end(); TEST_ASSERT_TRUE(r.ok()); if (complete) *complete = r.complete(); return r.releases(); } // The answers of git.twis.la, as they were on 2026-10-06. void test_latest_as_the_server_sends_it() { std::string json = file("latest.json"); for (size_t piece : {size_t(1), size_t(17), size_t(1000), json.size()}) { bool complete; auto releases = read(json, piece, 10, &complete); TEST_ASSERT_TRUE(complete); TEST_ASSERT_EQUAL_size_t(1, releases.size()); const Release& r = releases[0]; TEST_ASSERT_EQUAL_STRING("v0.10.0", r.tag.c_str()); TEST_ASSERT_EQUAL_STRING("2026-10-06", r.date().c_str()); TEST_ASSERT_EQUAL_STRING("https://git.twis.la/twisla/roro9stack/releases/download/v0.10.0/roro9stack-v0.10.0.ota", r.otaUrl.c_str()); TEST_ASSERT_EQUAL_UINT32(1885712, r.otaSize); TEST_ASSERT_TRUE(r.notes.rfind("v0.10.0: the Notes App", 0) == 0); TEST_ASSERT_TRUE(r.notes.find("\n\n") == std::string::npos); // the tag's message only TEST_ASSERT_TRUE(r.usable()); TEST_ASSERT_TRUE(trustedAssetUrl(r.otaUrl)); } } void test_a_list() { std::string json = file("list3.json"); for (size_t piece : {size_t(1), size_t(64), json.size()}) { auto releases = read(json, piece); TEST_ASSERT_EQUAL_size_t(3, releases.size()); TEST_ASSERT_EQUAL_STRING("v0.10.0", releases[0].tag.c_str()); TEST_ASSERT_EQUAL_STRING("v0.9.0", releases[1].tag.c_str()); TEST_ASSERT_EQUAL_STRING("v0.8.1", releases[2].tag.c_str()); for (auto& r : releases) { TEST_ASSERT_TRUE(r.usable()); TEST_ASSERT_TRUE(r.otaUrl.find("/" + r.tag + "/roro9stack-" + r.tag + ".ota") != std::string::npos); TEST_ASSERT_TRUE(r.otaSize > 1000000); } } TEST_ASSERT_EQUAL_size_t(2, read(json, 100, 2).size()); // no more than asked for } void test_what_makes_a_release_usable() { auto releases = read(R"([ {"tag_name":"v1.0.0","draft":true,"prerelease":false,"assets":[{"name":"roro9stack-v1.0.0.ota","size":5,"browser_download_url":"https://git.twis.la/twisla/roro9stack/releases/download/v1.0.0/roro9stack-v1.0.0.ota"}]}, {"tag_name":"v0.9.0-rc1","draft":false,"prerelease":true,"assets":[{"name":"roro9stack-v0.9.0-rc1.ota","size":5,"browser_download_url":"https://git.twis.la/twisla/roro9stack/releases/download/v0.9.0-rc1/roro9stack-v0.9.0-rc1.ota"}]}, {"tag_name":"v0.8.0","draft":false,"prerelease":false,"assets":[{"size":9,"browser_download_url":"https://git.twis.la/x/SHA256SUMS","name":"SHA256SUMS"}]}, {"assets":[{"browser_download_url":"https://git.twis.la/twisla/roro9stack/releases/download/v0.7.0/roro9stack-v0.7.0.ota","size":7,"name":"roro9stack-v0.7.0.ota"},{"name":"x.bin","size":1,"browser_download_url":"https://git.twis.la/x.bin"}],"draft":false,"tag_name":"v0.7.0","prerelease":false} ])", 5); TEST_ASSERT_EQUAL_size_t(4, releases.size()); TEST_ASSERT_FALSE(releases[0].usable()); // a draft TEST_ASSERT_FALSE(releases[1].usable()); // a pre-release TEST_ASSERT_FALSE(releases[2].usable()); // nothing to install in it TEST_ASSERT_TRUE(releases[3].usable()); // the fields in any order TEST_ASSERT_EQUAL_UINT32(7, releases[3].otaSize); } void test_notes() { TEST_ASSERT_EQUAL_STRING("one two", firstParagraph(" one two \n\nsecond", false).c_str()); TEST_ASSERT_EQUAL_STRING("one\ntwo", firstParagraph("one\ntwo\r\n\r\nthree", false).c_str()); TEST_ASSERT_EQUAL_STRING("only", firstParagraph("only", false).c_str()); TEST_ASSERT_EQUAL_STRING("cut...", firstParagraph("cut", true).c_str()); TEST_ASSERT_EQUAL_STRING("whole", firstParagraph("whole\n\nrest", true).c_str()); TEST_ASSERT_EQUAL_STRING("", firstParagraph("\n\n", false).c_str()); } void test_which_releases_are_updates() { Release r; r.tag = "v0.11.0"; r.otaUrl = "https://git.twis.la/twisla/roro9stack/releases/download/v0.11.0/roro9stack-v0.11.0.ota"; TEST_ASSERT_TRUE(isNewer(r, "v0.10.0")); TEST_ASSERT_TRUE(isNewer(r, "v0.10.0-14-gabc1234-dirty+debug")); // between releases: the next one is still newer TEST_ASSERT_FALSE(isNewer(r, "v0.11.0")); TEST_ASSERT_FALSE(isNewer(r, "v0.11.0-3-gabc1234")); // a build after the release isn't older than it TEST_ASSERT_FALSE(isNewer(r, "v0.12.0")); TEST_ASSERT_FALSE(isNewer(r, "unknown")); // a build that doesn't know its version isn't offered anything r.tag = "v0.10.1"; TEST_ASSERT_TRUE(isNewer(r, "v0.10.0")); r.tag = "v0.9.9"; TEST_ASSERT_FALSE(isNewer(r, "v0.10.0")); r.tag = "v0.11.0"; r.draft = true; TEST_ASSERT_FALSE(isNewer(r, "v0.10.0")); r.draft = false; TEST_ASSERT_TRUE(shouldAnnounce(r, "v0.10.0", "", "")); TEST_ASSERT_FALSE(shouldAnnounce(r, "v0.10.0", "v0.11.0", "")); // it rolled back here before TEST_ASSERT_FALSE(shouldAnnounce(r, "v0.10.0", "", "v0.11.0")); // already said so TEST_ASSERT_TRUE(shouldAnnounce(r, "v0.10.0", "v0.10.5", "v0.10.9")); } void test_only_the_projects_downloads_are_fetched() { const char* good = "https://git.twis.la/twisla/roro9stack/releases/download/v0.11.0/roro9stack-v0.11.0.ota"; TEST_ASSERT_TRUE(trustedAssetUrl(good)); TEST_ASSERT_TRUE(trustedAssetUrl("https://GIT.twis.la/twisla/roro9stack/releases/download/v1/a.ota")); TEST_ASSERT_FALSE(trustedAssetUrl("http://git.twis.la/twisla/roro9stack/releases/download/v1/a.ota")); TEST_ASSERT_FALSE(trustedAssetUrl("https://git.twis.la:8443/twisla/roro9stack/releases/download/v1/a.ota")); TEST_ASSERT_FALSE(trustedAssetUrl("https://evil.example/twisla/roro9stack/releases/download/v1/a.ota")); TEST_ASSERT_FALSE(trustedAssetUrl("https://git.twis.la.evil.example/twisla/roro9stack/releases/download/v1/a.ota")); TEST_ASSERT_FALSE(trustedAssetUrl("https://git.twis.la@evil.example/twisla/roro9stack/releases/download/v1/a.ota")); TEST_ASSERT_FALSE(trustedAssetUrl("https://git.twis.la/other/repo/releases/download/v1/a.ota")); TEST_ASSERT_FALSE(trustedAssetUrl("https://git.twis.la/twisla/roro9stack/releases/download/../../../x")); TEST_ASSERT_FALSE(trustedAssetUrl("https://git.twis.la/twisla/roro9stack/releases/download/v1/a.ota?x=1")); TEST_ASSERT_FALSE(trustedAssetUrl("")); } int main() { UNITY_BEGIN(); RUN_TEST(test_latest_as_the_server_sends_it); RUN_TEST(test_a_list); RUN_TEST(test_what_makes_a_release_usable); RUN_TEST(test_notes); RUN_TEST(test_which_releases_are_updates); RUN_TEST(test_only_the_projects_downloads_are_fetched); return UNITY_END(); }