--- /dev/null
+#
+# Input packet
+#
+Packet-Type = Access-Request
+User-Name = "binary-test"
+
+#
+# Expected answer. The binary payload (`Hello\0World\0\xff\0\xff` - 15
+# bytes, with embedded NULs and high-bit chars) is assigned onto the
+# control list from inside binary.unlang so the xlat path exercises an
+# attribute reference rather than a compile-time literal.
+#
+Packet-Type == Access-Accept
--- /dev/null
+#
+# Binary payload with embedded NUL bytes and high-bit chars. The
+# attribute in binary.attrs carries the raw bytes; the xlat-form
+# must preserve them end to end.
+#
+control.Tmp-Octets-0 := 0x48656c6c6f00576f726c6400ff00ff
+if (!%kafka.produce('freeradius-test-xlat', &control.Tmp-Octets-0)) {
+ test_fail
+}
+
+#
+# Empty payload via the method form - librdkafka accepts zero-length
+# records.
+#
+kafka.produce.freeradius-test-empty
+if (!ok) {
+ test_fail
+}
+
+#
+# Empty payload via the xlat form.
+#
+if (!%kafka.produce('freeradius-test-xlat', "")) {
+ test_fail
+}
+
+test_pass
--- /dev/null
+#
+# Input packet - the test harness wraps the script in
+# `recv Access-Request { ... }`, so bare `kafka` below resolves its
+# topic from that section's name2 ("Access-Request").
+#
+Packet-Type = Access-Request
+User-Name = "method-implicit"
+
+#
+# Expected answer
+#
+Packet-Type == Access-Accept
--- /dev/null
+#
+# Implicit-topic dispatch: bare `kafka` inside a `recv` section
+# resolves the topic from the section's name2. The harness runs this
+# file inside `recv Access-Request { ... }`, so the topic must be
+# declared as `Access-Request` in module.conf.
+#
+kafka
+if (!ok) {
+ test_fail
+}
+
+test_pass
--- /dev/null
+#
+# Input packet
+#
+Packet-Type = Access-Request
+User-Name = "invalid-test"
+
+#
+# Expected answer
+#
+Packet-Type == Access-Accept
--- /dev/null
+#
+# Edge-case inputs for the xlat - produces should still return a bool
+# cleanly (either success or false + logged error) without crashing.
+#
+
+#
+# Empty string payload.
+#
+if (!%kafka.produce('freeradius-test-xlat', "")) {
+ test_fail
+}
+
+#
+# Very long payload (16 KiB). Well within librdkafka's default
+# message.max.bytes (1 MiB) but enough to exercise the copy path.
+#
+if (!%kafka.produce('freeradius-test-xlat', %str.rpad("x", 16384, "x"))) {
+ test_fail
+}
+
+#
+# Embedded newlines, tabs, quotes - just special characters in the
+# string, all valid bytes.
+#
+if (!%kafka.produce('freeradius-test-xlat', "line1\nline2\ttabbed\r\n\"quoted\"")) {
+ test_fail
+}
+
+#
+# UTF-8 / high-bit characters.
+#
+if (!%kafka.produce('freeradius-test-xlat', "héllo — wörld 🚀")) {
+ test_fail
+}
+
+test_pass
--- /dev/null
+#
+# Input packet
+#
+Packet-Type = Access-Request
+User-Name = "keyed-test-user"
+
+#
+# Expected answer
+#
+Packet-Type == Access-Accept
--- /dev/null
+#
+# kafka.produce with a topic that declares both `value` and `key`.
+# The partition is derived deterministically from the key; the test
+# just asserts the produce completes successfully.
+#
+kafka.produce.freeradius-test-keyed
+if (!ok) {
+ test_fail
+}
+
+#
+# Same topic, same key - repeatable produce. Partitioning should be
+# stable, so two back-to-back produces with the same key land on the
+# same partition.
+#
+kafka.produce.freeradius-test-keyed
+if (!ok) {
+ test_fail
+}
+
+#
+# Topic without a declared `key` - produces still succeed; librdkafka
+# picks a partition via the configured partitioner.
+#
+kafka.produce.freeradius-test-method
+if (!ok) {
+ test_fail
+}
+
+test_pass
--- /dev/null
+#
+# Input packet
+#
+Packet-Type = Access-Request
+User-Name = "method-explicit"
+
+#
+# Expected answer
+#
+Packet-Type == Access-Accept
--- /dev/null
+#
+# kafka.produce.<topic> - method form with explicit name2.
+#
+# The topic is the method's second identifier; `value` comes from the
+# topic's per-topic `value = ...` setting in module.conf.
+#
+kafka.produce.freeradius-test-method
+if (!ok) {
+ test_fail
+}
+
+#
+# The `send` and `recv` aliases dispatch to the same code path as
+# `produce` - same call, just a different name for readability.
+#
+kafka.send.freeradius-test-method
+if (!ok) {
+ test_fail
+}
+
+kafka.recv.freeradius-test-method
+if (!ok) {
+ test_fail
+}
+
+test_pass
# than silently creating new topics.
#
topic {
+ #
+ # Targeted by the xlat tests; `value` is supplied via the xlat
+ # argument list.
+ #
freeradius-test-base {
}
freeradius-test-xlat {
}
freeradius-test-xlat-alt {
}
+
+ #
+ # Method-form tests. Per-topic `value` / `key` are read from
+ # these subsections at call_env parse time.
+ #
+
+ # Basic method invocation, no key.
+ freeradius-test-method {
+ value = "%{User-Name}"
+ }
+
+ # With a key; delivery should partition deterministically.
+ freeradius-test-keyed {
+ value = "payload=%{User-Name}"
+ key = &User-Name
+ }
+
+ # Empty value - librdkafka accepts zero-length payloads.
+ freeradius-test-empty {
+ value = ""
+ }
+
+ #
+ # Topic named after the packet type so the "implicit"
+ # dispatch (bare `kafka` inside `recv Access-Request`)
+ # resolves here without having to name a topic explicitly.
+ #
+ Access-Request {
+ value = "recv-implicit %{User-Name}"
+ }
}
flush_timeout = 5s