// Host tests for the WebConfig key allowlist / secret / slot-prefix helpers
// (src/helpers/WebConfigKeys.h). These parse attacker-supplied POST keys, so
// coverage of the length-guard and boundary cases matters for safety.
#include <gtest/gtest.h>
#include "helpers/WebConfigKeys.h"

// ---- allowlist ------------------------------------------------------------

TEST(WebConfigKeys, AllowsKnownScalarKeys) {
  EXPECT_TRUE(wcIsAllowedSetKey("name"));
  EXPECT_TRUE(wcIsAllowedSetKey("radio"));
  EXPECT_TRUE(wcIsAllowedSetKey("repeat"));
  EXPECT_TRUE(wcIsAllowedSetKey("wifi.ssid"));
  EXPECT_TRUE(wcIsAllowedSetKey("mqtt.iata"));
  EXPECT_TRUE(wcIsAllowedSetKey("mqtt.neighbors"));
  EXPECT_TRUE(wcIsAllowedSetKey("mqtt.neighbors.interval"));
  EXPECT_TRUE(wcIsAllowedSetKey("snmp.community"));
  EXPECT_TRUE(wcIsAllowedSetKey("timezone.offset"));
}

TEST(WebConfigKeys, AllowsPerSlotKeys) {
  EXPECT_TRUE(wcIsAllowedSetKey("mqtt1.preset"));
  EXPECT_TRUE(wcIsAllowedSetKey("mqtt1.server"));
  EXPECT_TRUE(wcIsAllowedSetKey("mqtt1.token"));
  EXPECT_TRUE(wcIsAllowedSetKey("mqtt6.audience"));  // MAX_MQTT_SLOTS == 6
}

TEST(WebConfigKeys, RejectsDangerousOrUnknownKeys) {
  EXPECT_FALSE(wcIsAllowedSetKey("erase"));
  EXPECT_FALSE(wcIsAllowedSetKey("password"));
  EXPECT_FALSE(wcIsAllowedSetKey("reboot"));
  EXPECT_FALSE(wcIsAllowedSetKey("bogus"));
  EXPECT_FALSE(wcIsAllowedSetKey("mqtt1.bogus"));   // unknown slot field
  EXPECT_FALSE(wcIsAllowedSetKey(""));
}

TEST(WebConfigKeys, SlotIndexBoundsMatchMaxSlots) {
  EXPECT_FALSE(wcIsAllowedSetKey("mqtt0.preset"));  // slot 0 invalid
  EXPECT_TRUE(wcIsAllowedSetKey("mqtt6.preset"));   // last valid slot
  EXPECT_FALSE(wcIsAllowedSetKey("mqtt7.preset"));  // beyond MAX_MQTT_SLOTS
  EXPECT_FALSE(wcIsAllowedSetKey("mqtt9.preset"));
}

TEST(WebConfigKeys, IsCaseSensitive) {
  EXPECT_FALSE(wcIsAllowedSetKey("Name"));
  EXPECT_FALSE(wcIsAllowedSetKey("MQTT1.preset"));
}

// ---- short-key OOB guard --------------------------------------------------
// The slot-prefix probe indexes key[4..6]; these short strings must be rejected
// without ever reading past the terminator.

TEST(WebConfigKeys, ShortKeysRejectedSafely) {
  EXPECT_FALSE(wcIsSlotKeyPrefix(""));
  EXPECT_FALSE(wcIsSlotKeyPrefix("m"));
  EXPECT_FALSE(wcIsSlotKeyPrefix("mq"));
  EXPECT_FALSE(wcIsSlotKeyPrefix("mqt"));
  EXPECT_FALSE(wcIsSlotKeyPrefix("mqtt"));     // 4 chars — no digit/dot
  EXPECT_FALSE(wcIsSlotKeyPrefix("mqtt1"));    // 5 chars — no dot
  EXPECT_FALSE(wcIsSlotKeyPrefix("mqtt1."));   // 6 chars — no field char
  EXPECT_TRUE(wcIsSlotKeyPrefix("mqtt1.x"));   // 7 chars — minimum valid
  // Same guard via the public allowlist/secret entry points:
  EXPECT_FALSE(wcIsAllowedSetKey("mqtt"));
  EXPECT_FALSE(wcIsSecretKey("m"));
  EXPECT_FALSE(wcIsSecretKey("mqtt"));
}

TEST(WebConfigKeys, SlotPrefixDigitRange) {
  EXPECT_FALSE(wcIsSlotKeyPrefix("mqtt0.x"));
  EXPECT_TRUE(wcIsSlotKeyPrefix("mqtt6.x"));
  EXPECT_FALSE(wcIsSlotKeyPrefix("mqtt7.x"));
  EXPECT_FALSE(wcIsSlotKeyPrefix("mqttA.x"));  // non-digit
}

// ---- secret classification ------------------------------------------------

TEST(WebConfigKeys, SecretKeysDetected) {
  EXPECT_TRUE(wcIsSecretKey("wifi.pwd"));
  EXPECT_TRUE(wcIsSecretKey("mqtt1.password"));
  EXPECT_TRUE(wcIsSecretKey("mqtt3.token"));
  EXPECT_TRUE(wcIsSecretKey("mqtt6.password"));
}

TEST(WebConfigKeys, NonSecretKeysNotFlagged) {
  EXPECT_FALSE(wcIsSecretKey("wifi.ssid"));
  EXPECT_FALSE(wcIsSecretKey("mqtt1.username"));  // username is not masked
  EXPECT_FALSE(wcIsSecretKey("mqtt1.server"));
  EXPECT_FALSE(wcIsSecretKey("mqtt.origin"));
  EXPECT_FALSE(wcIsSecretKey("name"));
  EXPECT_FALSE(wcIsSecretKey(""));
}

TEST(WebConfigKeys, EverySecretKeyIsAlsoAllowed) {
  // A secret key must be one the portal can actually set, or the masking is moot.
  const char* secrets[] = {"wifi.pwd", "mqtt1.password", "mqtt1.token",
                           "mqtt6.password", "mqtt6.token"};
  for (const char* k : secrets) {
    EXPECT_TRUE(wcIsSecretKey(k)) << k;
    EXPECT_TRUE(wcIsAllowedSetKey(k)) << k;
  }
}

// ---- request correlation -------------------------------------------------

TEST(WebConfigKeys, AcceptsExactHexRequestIds) {
  EXPECT_TRUE(wcIsValidReqId("0123456789abcdef"));
  EXPECT_TRUE(wcIsValidReqId("ABCDEF0123456789"));
}

TEST(WebConfigKeys, RejectsMissingMalformedOrWrongLengthRequestIds) {
  EXPECT_FALSE(wcIsValidReqId(NULL));
  EXPECT_FALSE(wcIsValidReqId(""));
  EXPECT_FALSE(wcIsValidReqId("0123456789abcde"));
  EXPECT_FALSE(wcIsValidReqId("0123456789abcdef0"));
  EXPECT_FALSE(wcIsValidReqId("0123456789abcdeg"));
  EXPECT_FALSE(wcIsValidReqId("01234567-9abcdef"));
  EXPECT_FALSE(wcIsValidReqId("01234567 9abcdef"));
}

int main(int argc, char** argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
