Commit f0847500 authored by jan.koester's avatar jan.koester
Browse files

test

parent f18bbd91
Loading
Loading
Loading
Loading
Loading
+129 −0
Original line number Diff line number Diff line
@@ -630,3 +630,132 @@ TEST_F(ClusterPerformanceTest, ClusterVsFileComparison) {
    test_unlink(cmpDbPath.c_str());
    test_unlink((cmpDbPath + ".lock").c_str());
}

// ═════════════════════════════════════════════════════════════
//  6.  Prefetch never blocks (non-blocking async retrieve)
//
//  Bug: The old implementation did a synchronous 5s wait on
//  client->retrieve() inside fetchFromCluster(). Every other
//  request would hit the 5s timeout, blocking the HTTP thread.
//  Fix: retrieve is fire-and-forget; the result is picked up
//  on the next prefetch() call.  Each prefetch() must complete
//  in well under 1 second.
// ═════════════════════════════════════════════════════════════

TEST_F(ClusterPerformanceTest, PrefetchNeverBlocks) {
    constexpr size_t ROUNDS = 20;
    constexpr double MAX_MS = 1000.0;  // must finish in <1s (old bug: 5s)

    std::vector<double> times_ms;

    for (size_t i = 0; i < ROUNDS; ++i) {
        // Each round: fresh ClusterBackend (simulates a new HTTP request).
        // Force a refresh so it actually attempts a cluster fetch, not just cache.
        AuthBackend cb(authdb::ClusterStore, "", "prefetch_test_domain");

        auto t0 = Clock::now();
        cb.lock();
        cb.unlock();
        auto t1 = Clock::now();

        double ms = std::chrono::duration<double, std::milli>(t1 - t0).count();
        times_ms.push_back(ms);
    }

    double maxTime = *std::max_element(times_ms.begin(), times_ms.end());
    double avgTime = std::accumulate(times_ms.begin(), times_ms.end(), 0.0) / times_ms.size();

    std::cout << "\n=== Prefetch Non-Blocking Test (" << ROUNDS << " rounds) ===\n"
              << "  avg = " << avgTime << " ms\n"
              << "  max = " << maxTime << " ms  (limit: " << MAX_MS << " ms)\n";

    // The critical assertion: no single prefetch may block ≥1s.
    // Before the fix, ~50% would hit the 5s timeout.
    EXPECT_LT(maxTime, MAX_MS)
        << "prefetch() blocked for " << maxTime << " ms — async retrieve may be synchronous";
}

// ═════════════════════════════════════════════════════════════
//  7.  Revision guard prevents stale data overwrite
//
//  Bug: If a recovering cluster node returned a manifest with
//  an older revision, fetchFromCluster() would blindly replace
//  the local cache → data loss.
//  Fix: fetchFromCluster() compares the manifest revision to
//  the cached revision and discards stale data.
// ═════════════════════════════════════════════════════════════

TEST_F(ClusterPerformanceTest, RevisionGuardNoDataLoss) {
    const std::string domain = "revguard_" + std::to_string(test_getpid());

    // Phase 1: Create 10 users via cluster
    constexpr size_t N = 10;
    std::vector<uuid::uuid> uids(N);
    {
        AuthBackend cb(authdb::ClusterStore, "", domain);
        User user;
        for (size_t i = 0; i < N; ++i) {
            uids[i].generate();
            class authdb::UserData udat(uids[i]);
            udat.setUserName("revuser_" + std::to_string(i));
            udat.setPwHash("pw" + std::to_string(i));
            user.create(cb, &udat);
        }
    }

    // Small delay to let async push propagate
    std::this_thread::sleep_for(std::chrono::milliseconds(500));

    // Phase 2: Read back revision and user count
    size_t rev_after_create = 0;
    size_t user_count = 0;
    {
        AuthBackend cb(authdb::ClusterStore, "", domain);
        cb.lock();
        rev_after_create = cb.getRevesion();
        User user;
        std::vector<uuid::uuid> listed;
        user.list(cb, listed);
        user_count = listed.size();
        cb.unlock();
    }

    EXPECT_EQ(user_count, N)
        << "Expected " << N << " users but found " << user_count;
    EXPECT_GT(rev_after_create, 0u)
        << "Revision should be > 0 after creating users";

    // Phase 3: Force multiple rapid prefetch cycles — revision must never decrease
    size_t min_rev = rev_after_create;
    size_t max_rev = rev_after_create;
    size_t min_users = user_count;

    for (int round = 0; round < 10; ++round) {
        AuthBackend cb(authdb::ClusterStore, "", domain);
        cb.lock();
        size_t rev = cb.getRevesion();
        User user;
        std::vector<uuid::uuid> listed;
        user.list(cb, listed);
        cb.unlock();

        if (rev < min_rev) min_rev = rev;
        if (rev > max_rev) max_rev = rev;
        if (listed.size() < min_users) min_users = listed.size();
    }

    std::cout << "\n=== Revision Guard Test ===\n"
              << "  rev after create = " << rev_after_create << "\n"
              << "  rev range        = [" << min_rev << ", " << max_rev << "]\n"
              << "  min users seen   = " << min_users << " (expected " << N << ")\n";

    // Revision must never go backward
    EXPECT_GE(min_rev, rev_after_create)
        << "Revision went backward: saw " << min_rev << " but expected >= " << rev_after_create
        << " — stale manifest was accepted";

    // Users must never disappear
    EXPECT_EQ(min_users, N)
        << "Lost users: saw " << min_users << " but expected " << N
        << " — stale manifest overwrote cache";
}