]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
tests/kafka: cover method dispatch forms, keys, binary and edge payloads
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 22 Apr 2026 02:17:30 +0000 (22:17 -0400)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 22 Apr 2026 03:48:39 +0000 (23:48 -0400)
Extend the kafka test harness to exercise:

 - method form with explicit name2 (kafka.produce / send / recv .topic)
 - method form with implicit name2 (bare 'kafka' inside a recv section
   that matches a topic named after the packet type)
 - keyed produces (deterministic partitioning by key attr)
 - binary payloads with embedded NULs and high-bit bytes
 - edge-case xlat inputs (empty string, 16 KiB payload, embedded
   control characters, UTF-8 / multibyte)

Topics referenced by the new tests are declared in module.conf so
unknown-topic handling continues to fail at parse time.

src/tests/modules/kafka/binary.attrs [new file with mode: 0644]
src/tests/modules/kafka/binary.unlang [new file with mode: 0644]
src/tests/modules/kafka/implicit.attrs [new file with mode: 0644]
src/tests/modules/kafka/implicit.unlang [new file with mode: 0644]
src/tests/modules/kafka/invalid.attrs [new file with mode: 0644]
src/tests/modules/kafka/invalid.unlang [new file with mode: 0644]
src/tests/modules/kafka/keyed.attrs [new file with mode: 0644]
src/tests/modules/kafka/keyed.unlang [new file with mode: 0644]
src/tests/modules/kafka/method.attrs [new file with mode: 0644]
src/tests/modules/kafka/method.unlang [new file with mode: 0644]
src/tests/modules/kafka/module.conf

diff --git a/src/tests/modules/kafka/binary.attrs b/src/tests/modules/kafka/binary.attrs
new file mode 100644 (file)
index 0000000..7dd514d
--- /dev/null
@@ -0,0 +1,13 @@
+#
+#  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
diff --git a/src/tests/modules/kafka/binary.unlang b/src/tests/modules/kafka/binary.unlang
new file mode 100644 (file)
index 0000000..1a4d573
--- /dev/null
@@ -0,0 +1,27 @@
+#
+#  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
diff --git a/src/tests/modules/kafka/implicit.attrs b/src/tests/modules/kafka/implicit.attrs
new file mode 100644 (file)
index 0000000..e606bdc
--- /dev/null
@@ -0,0 +1,12 @@
+#
+#  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
diff --git a/src/tests/modules/kafka/implicit.unlang b/src/tests/modules/kafka/implicit.unlang
new file mode 100644 (file)
index 0000000..4112415
--- /dev/null
@@ -0,0 +1,12 @@
+#
+#  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
diff --git a/src/tests/modules/kafka/invalid.attrs b/src/tests/modules/kafka/invalid.attrs
new file mode 100644 (file)
index 0000000..7ce9848
--- /dev/null
@@ -0,0 +1,10 @@
+#
+#  Input packet
+#
+Packet-Type = Access-Request
+User-Name = "invalid-test"
+
+#
+#  Expected answer
+#
+Packet-Type == Access-Accept
diff --git a/src/tests/modules/kafka/invalid.unlang b/src/tests/modules/kafka/invalid.unlang
new file mode 100644 (file)
index 0000000..7e87da5
--- /dev/null
@@ -0,0 +1,36 @@
+#
+#  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
diff --git a/src/tests/modules/kafka/keyed.attrs b/src/tests/modules/kafka/keyed.attrs
new file mode 100644 (file)
index 0000000..560a14c
--- /dev/null
@@ -0,0 +1,10 @@
+#
+#  Input packet
+#
+Packet-Type = Access-Request
+User-Name = "keyed-test-user"
+
+#
+#  Expected answer
+#
+Packet-Type == Access-Accept
diff --git a/src/tests/modules/kafka/keyed.unlang b/src/tests/modules/kafka/keyed.unlang
new file mode 100644 (file)
index 0000000..707caef
--- /dev/null
@@ -0,0 +1,30 @@
+#
+#  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
diff --git a/src/tests/modules/kafka/method.attrs b/src/tests/modules/kafka/method.attrs
new file mode 100644 (file)
index 0000000..6746303
--- /dev/null
@@ -0,0 +1,10 @@
+#
+#  Input packet
+#
+Packet-Type = Access-Request
+User-Name = "method-explicit"
+
+#
+#  Expected answer
+#
+Packet-Type == Access-Accept
diff --git a/src/tests/modules/kafka/method.unlang b/src/tests/modules/kafka/method.unlang
new file mode 100644 (file)
index 0000000..34a6ccd
--- /dev/null
@@ -0,0 +1,26 @@
+#
+#  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
index bd6cb7ac57b808e3457c846a8fec4d2487cfbd46..694346530155eda84e30f898362b057db70bc662 100644 (file)
@@ -14,12 +14,46 @@ kafka {
        #  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