|
| 1 | +#include <gtest/gtest.h> |
| 2 | + |
| 3 | +#include "instance_id.hpp" |
| 4 | + |
| 5 | +#include <launchdarkly/server_side/client.hpp> |
| 6 | +#include <launchdarkly/server_side/config/config_builder.hpp> |
| 7 | + |
| 8 | +#include <cstddef> |
| 9 | +#include <regex> |
| 10 | +#include <set> |
| 11 | +#include <string> |
| 12 | + |
| 13 | +using namespace launchdarkly; |
| 14 | +using namespace launchdarkly::server_side; |
| 15 | + |
| 16 | +namespace { |
| 17 | + |
| 18 | +// Matches a canonical UUID v4 in lowercase hex: |
| 19 | +// xxxxxxxx-xxxx-4xxx-Yxxx-xxxxxxxxxxxx |
| 20 | +// where 'Y' is one of 8, 9, a, or b (RFC 4122 variant). |
| 21 | +bool IsUuidV4(std::string const& s) { |
| 22 | + static std::regex const re( |
| 23 | + "^[0-9a-f]{8}-" |
| 24 | + "[0-9a-f]{4}-" |
| 25 | + "4[0-9a-f]{3}-" |
| 26 | + "[89ab][0-9a-f]{3}-" |
| 27 | + "[0-9a-f]{12}$"); |
| 28 | + return std::regex_match(s, re); |
| 29 | +} |
| 30 | + |
| 31 | +} // namespace |
| 32 | + |
| 33 | +// Spec: SCMP-server-connection-minutes-polling section 1.1 requires the |
| 34 | +// X-LaunchDarkly-Instance-Id value to be a v4 UUID. |
| 35 | +TEST(InstanceIdTest, GeneratedValueIsUuidV4) { |
| 36 | + auto id = MakeInstanceId(); |
| 37 | + ASSERT_FALSE(id.empty()) << "MakeInstanceId returned an empty string"; |
| 38 | + EXPECT_TRUE(IsUuidV4(id)) |
| 39 | + << "MakeInstanceId returned " << id << " which is not a v4 UUID"; |
| 40 | +} |
| 41 | + |
| 42 | +// Each invocation must yield a different value; spec requires "the GUID MUST |
| 43 | +// be used uniquely for this purpose". |
| 44 | +TEST(InstanceIdTest, GeneratedValuesAreUnique) { |
| 45 | + constexpr int kSamples = 100; |
| 46 | + std::set<std::string> seen; |
| 47 | + for (int i = 0; i < kSamples; ++i) { |
| 48 | + auto id = MakeInstanceId(); |
| 49 | + ASSERT_FALSE(id.empty()); |
| 50 | + EXPECT_TRUE(seen.insert(id).second) |
| 51 | + << "duplicate UUID emitted from MakeInstanceId: " << id; |
| 52 | + } |
| 53 | + EXPECT_EQ(seen.size(), static_cast<std::size_t>(kSamples)); |
| 54 | +} |
| 55 | + |
| 56 | +// The header name constant must match the spec verbatim. This guards against |
| 57 | +// accidental renaming (the header name is part of the wire contract). |
| 58 | +TEST(InstanceIdTest, HeaderNameMatchesSpec) { |
| 59 | + EXPECT_STREQ(kInstanceIdHeader, "X-LaunchDarkly-Instance-Id"); |
| 60 | +} |
| 61 | + |
| 62 | +// Sanity-check that a Client can be constructed; the integration that the |
| 63 | +// instance-id header actually ends up on outbound requests is covered by the |
| 64 | +// cross-SDK contract test harness (capability: "instance-id"). |
| 65 | +TEST(InstanceIdTest, ClientConstructsWithInstanceIdHeader) { |
| 66 | + // Building a Client exercises the code path that stamps the instance-id |
| 67 | + // header into the shared HttpProperties. We can't observe the header |
| 68 | + // directly from the public API, so this test simply asserts that the |
| 69 | + // construction succeeds; the spec-level guarantees about the header value |
| 70 | + // are exercised by GeneratedValueIsUuidV4 and GeneratedValuesAreUnique, |
| 71 | + // and the on-the-wire guarantee is covered by the cross-SDK contract test |
| 72 | + // harness (capability: "instance-id"). |
| 73 | + Client client(ConfigBuilder("sdk-123").Build().value()); |
| 74 | + EXPECT_NE(client.Version(), nullptr); |
| 75 | +} |
0 commit comments