|
|
|
|
@@ -28,7 +28,9 @@
|
|
|
|
|
static ff_parsed_line_t*
|
|
|
|
|
_roundtrip(const char* timestamp, const char* type, const char* enc,
|
|
|
|
|
const char* stanza_id, const char* archive_id, const char* replace_id,
|
|
|
|
|
const char* from_jid, const char* from_resource, const char* message)
|
|
|
|
|
const char* from_jid, const char* from_resource,
|
|
|
|
|
const char* to_jid, const char* to_resource, int marked_read,
|
|
|
|
|
const char* message)
|
|
|
|
|
{
|
|
|
|
|
char tmppath[] = "/tmp/proftest_rt_XXXXXX";
|
|
|
|
|
int fd = mkstemp(tmppath);
|
|
|
|
|
@@ -39,7 +41,9 @@ _roundtrip(const char* timestamp, const char* type, const char* enc,
|
|
|
|
|
|
|
|
|
|
ff_write_line(fp, timestamp, type, enc,
|
|
|
|
|
stanza_id, archive_id, replace_id,
|
|
|
|
|
from_jid, from_resource, message);
|
|
|
|
|
from_jid, from_resource,
|
|
|
|
|
to_jid, to_resource, marked_read,
|
|
|
|
|
message);
|
|
|
|
|
fclose(fp);
|
|
|
|
|
|
|
|
|
|
/* Read & parse */
|
|
|
|
|
@@ -70,7 +74,9 @@ test_ff_roundtrip_simple_chat(void** state)
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-06-15T12:30:00+00:00", "chat", "none",
|
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
|
"alice@example.com", NULL, "Hello, world!");
|
|
|
|
|
"alice@example.com", NULL,
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"Hello, world!");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->timestamp_str, "2025-06-15T12:30:00+00:00");
|
|
|
|
|
@@ -90,7 +96,9 @@ test_ff_roundtrip_with_all_metadata(void** state)
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-06-15T12:30:00+00:00", "chat", "omemo",
|
|
|
|
|
"sid-abc-123", "aid-xyz-789", "corrects-old-id",
|
|
|
|
|
"bob@example.com", "phone", "Encrypted message.");
|
|
|
|
|
"bob@example.com", "phone",
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"Encrypted message.");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->stanza_id, "sid-abc-123");
|
|
|
|
|
@@ -109,7 +117,9 @@ test_ff_roundtrip_with_resource(void** state)
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-01-01T00:00:00Z", "chat", "none",
|
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
|
"user@jabber.org", "Profanity.abc123", "hi");
|
|
|
|
|
"user@jabber.org", "Profanity.abc123",
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"hi");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->from_jid, "user@jabber.org");
|
|
|
|
|
@@ -124,7 +134,9 @@ test_ff_roundtrip_newline_in_body(void** state)
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-03-01T10:00:00Z", "chat", "none",
|
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
|
"alice@x.com", NULL, "line1\nline2\nline3");
|
|
|
|
|
"alice@x.com", NULL,
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"line1\nline2\nline3");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->message, "line1\nline2\nline3");
|
|
|
|
|
@@ -138,7 +150,9 @@ test_ff_roundtrip_pipe_in_stanza_id(void** state)
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-03-01T10:00:00Z", "chat", "none",
|
|
|
|
|
"id|with|pipes", "aid|also|has|pipes", NULL,
|
|
|
|
|
"a@b.com", NULL, "test");
|
|
|
|
|
"a@b.com", NULL,
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"test");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->stanza_id, "id|with|pipes");
|
|
|
|
|
@@ -152,7 +166,9 @@ test_ff_roundtrip_backslash_in_body(void** state)
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-03-01T10:00:00Z", "chat", "none",
|
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
|
"a@b.com", NULL, "path\\to\\file C:\\Users\\test");
|
|
|
|
|
"a@b.com", NULL,
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"path\\to\\file C:\\Users\\test");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->message, "path\\to\\file C:\\Users\\test");
|
|
|
|
|
@@ -165,7 +181,9 @@ test_ff_roundtrip_unicode_body(void** state)
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-03-01T10:00:00Z", "chat", "none",
|
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
|
"a@b.com", NULL, "Привет мир 🌍 日本語テスト");
|
|
|
|
|
"a@b.com", NULL,
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"Привет мир 🌍 日本語テスト");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->message, "Привет мир 🌍 日本語テスト");
|
|
|
|
|
@@ -178,7 +196,9 @@ test_ff_roundtrip_empty_body(void** state)
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-03-01T10:00:00Z", "chat", "none",
|
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
|
"a@b.com", NULL, "");
|
|
|
|
|
"a@b.com", NULL,
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->message, "");
|
|
|
|
|
@@ -192,7 +212,9 @@ test_ff_roundtrip_colonspace_in_resource(void** state)
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-03-01T10:00:00Z", "chat", "none",
|
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
|
"a@b.com", "res: with: colons", "msg");
|
|
|
|
|
"a@b.com", "res: with: colons",
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"msg");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->from_jid, "a@b.com");
|
|
|
|
|
@@ -207,7 +229,9 @@ test_ff_roundtrip_muc_type(void** state)
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-03-01T10:00:00Z", "muc", "none",
|
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
|
"room@conference.x.com", "nick", "hello room");
|
|
|
|
|
"room@conference.x.com", "nick",
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"hello room");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->type, "muc");
|
|
|
|
|
@@ -222,7 +246,9 @@ test_ff_roundtrip_omemo_enc(void** state)
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-03-01T10:00:00Z", "chat", "omemo",
|
|
|
|
|
"sid-1", NULL, NULL,
|
|
|
|
|
"a@b.com", NULL, "secret");
|
|
|
|
|
"a@b.com", NULL,
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"secret");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->enc, "omemo");
|
|
|
|
|
@@ -236,7 +262,9 @@ test_ff_roundtrip_replace_id(void** state)
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-03-01T10:00:00Z", "chat", "none",
|
|
|
|
|
"new-id", NULL, "old-id-to-correct",
|
|
|
|
|
"a@b.com", NULL, "corrected text");
|
|
|
|
|
"a@b.com", NULL,
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"corrected text");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->stanza_id, "new-id");
|
|
|
|
|
@@ -245,6 +273,55 @@ test_ff_roundtrip_replace_id(void** state)
|
|
|
|
|
ff_parsed_line_free(pl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
test_ff_roundtrip_to_jid_and_marked_read(void** state)
|
|
|
|
|
{
|
|
|
|
|
/* to_jid, to_resource, and marked_read round-trip */
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-06-15T12:30:00+00:00", "chat", "none",
|
|
|
|
|
"sid-1", NULL, NULL,
|
|
|
|
|
"alice@example.com", "phone",
|
|
|
|
|
"bob@example.com", "laptop", 1,
|
|
|
|
|
"Hello Bob!");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->from_jid, "alice@example.com");
|
|
|
|
|
assert_string_equal(pl->from_resource, "phone");
|
|
|
|
|
assert_string_equal(pl->to_jid, "bob@example.com");
|
|
|
|
|
assert_string_equal(pl->to_resource, "laptop");
|
|
|
|
|
assert_int_equal(pl->marked_read, 1);
|
|
|
|
|
assert_string_equal(pl->message, "Hello Bob!");
|
|
|
|
|
ff_parsed_line_free(pl);
|
|
|
|
|
|
|
|
|
|
/* marked_read = 0 (unread) */
|
|
|
|
|
pl = _roundtrip(
|
|
|
|
|
"2025-06-15T12:31:00+00:00", "chat", "none",
|
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
|
"bob@example.com", NULL,
|
|
|
|
|
"alice@example.com", NULL, 0,
|
|
|
|
|
"Reply");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->to_jid, "alice@example.com");
|
|
|
|
|
assert_null(pl->to_resource);
|
|
|
|
|
assert_int_equal(pl->marked_read, 0);
|
|
|
|
|
ff_parsed_line_free(pl);
|
|
|
|
|
|
|
|
|
|
/* marked_read = -1 (unset) — should NOT appear in output */
|
|
|
|
|
pl = _roundtrip(
|
|
|
|
|
"2025-06-15T12:32:00+00:00", "chat", "none",
|
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
|
"a@b.com", NULL,
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"no read flag");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_null(pl->to_jid);
|
|
|
|
|
assert_null(pl->to_resource);
|
|
|
|
|
assert_int_equal(pl->marked_read, -1);
|
|
|
|
|
ff_parsed_line_free(pl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ================================================================
|
|
|
|
|
* Escape / unescape symmetry tests
|
|
|
|
|
* ================================================================ */
|
|
|
|
|
@@ -442,3 +519,203 @@ test_ff_enc_str_roundtrip(void** state)
|
|
|
|
|
assert_int_equal(PROF_MSG_ENC_OX, ff_get_message_enc_type(ff_get_message_enc_str(PROF_MSG_ENC_OX)));
|
|
|
|
|
assert_int_equal(PROF_MSG_ENC_OMEMO, ff_get_message_enc_type(ff_get_message_enc_str(PROF_MSG_ENC_OMEMO)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ================================================================
|
|
|
|
|
* Additional round-trip tests
|
|
|
|
|
* ================================================================ */
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
test_ff_roundtrip_bracket_in_stanza_id(void** state)
|
|
|
|
|
{
|
|
|
|
|
/* ']' in stanza_id must be escaped as \] to avoid breaking metadata parser */
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-03-01T10:00:00Z", "chat", "none",
|
|
|
|
|
"id]with]brackets", "aid]also]has]brackets", NULL,
|
|
|
|
|
"a@b.com", NULL,
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"test brackets");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->stanza_id, "id]with]brackets");
|
|
|
|
|
assert_string_equal(pl->archive_id, "aid]also]has]brackets");
|
|
|
|
|
assert_string_equal(pl->message, "test brackets");
|
|
|
|
|
ff_parsed_line_free(pl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
test_ff_roundtrip_backslash_in_resource(void** state)
|
|
|
|
|
{
|
|
|
|
|
/* backslash in from_resource must survive round-trip */
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-03-01T10:00:00Z", "chat", "none",
|
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
|
"a@b.com", "res\\with\\backslash",
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"msg");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->from_jid, "a@b.com");
|
|
|
|
|
assert_string_equal(pl->from_resource, "res\\with\\backslash");
|
|
|
|
|
assert_string_equal(pl->message, "msg");
|
|
|
|
|
ff_parsed_line_free(pl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
test_ff_roundtrip_mucpm_type(void** state)
|
|
|
|
|
{
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-03-01T10:00:00Z", "mucpm", "none",
|
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
|
"room@conference.x.com", "nick",
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"private message");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->type, "mucpm");
|
|
|
|
|
assert_string_equal(pl->from_resource, "nick");
|
|
|
|
|
ff_parsed_line_free(pl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
test_ff_roundtrip_all_enc_types(void** state)
|
|
|
|
|
{
|
|
|
|
|
const char* enc_types[] = { "none", "otr", "pgp", "ox", "omemo" };
|
|
|
|
|
for (size_t i = 0; i < sizeof(enc_types) / sizeof(enc_types[0]); i++) {
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-03-01T10:00:00Z", "chat", enc_types[i],
|
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
|
"a@b.com", NULL,
|
|
|
|
|
NULL, NULL, -1,
|
|
|
|
|
"msg");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->enc, enc_types[i]);
|
|
|
|
|
ff_parsed_line_free(pl);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
test_ff_roundtrip_crlf_handling(void** state)
|
|
|
|
|
{
|
|
|
|
|
/* Write a line, then read it with \r\n ending — parser should strip \r */
|
|
|
|
|
char tmppath[] = "/tmp/proftest_crlf_XXXXXX";
|
|
|
|
|
int fd = mkstemp(tmppath);
|
|
|
|
|
assert_true(fd >= 0);
|
|
|
|
|
|
|
|
|
|
FILE* fp = fdopen(fd, "w");
|
|
|
|
|
assert_non_null(fp);
|
|
|
|
|
|
|
|
|
|
/* Write a raw line with \r\n ending */
|
|
|
|
|
fprintf(fp, "2025-03-01T10:00:00Z [chat|none] a@b.com: hello\r\n");
|
|
|
|
|
fclose(fp);
|
|
|
|
|
|
|
|
|
|
fp = fopen(tmppath, "r");
|
|
|
|
|
assert_non_null(fp);
|
|
|
|
|
|
|
|
|
|
gboolean truncated = FALSE;
|
|
|
|
|
char* buf = ff_readline(fp, &truncated);
|
|
|
|
|
fclose(fp);
|
|
|
|
|
unlink(tmppath);
|
|
|
|
|
|
|
|
|
|
assert_non_null(buf);
|
|
|
|
|
ff_parsed_line_t* pl = ff_parse_line(buf);
|
|
|
|
|
free(buf);
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->from_jid, "a@b.com");
|
|
|
|
|
assert_string_equal(pl->message, "hello");
|
|
|
|
|
ff_parsed_line_free(pl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
test_ff_roundtrip_to_jid_special_chars(void** state)
|
|
|
|
|
{
|
|
|
|
|
/* to: and to_res: with pipe and bracket chars that need escaping */
|
|
|
|
|
ff_parsed_line_t* pl = _roundtrip(
|
|
|
|
|
"2025-03-01T10:00:00Z", "chat", "none",
|
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
|
"a@b.com", NULL,
|
|
|
|
|
"to|user@c.com", "res]with|special", -1,
|
|
|
|
|
"msg with special to");
|
|
|
|
|
|
|
|
|
|
assert_non_null(pl);
|
|
|
|
|
assert_string_equal(pl->to_jid, "to|user@c.com");
|
|
|
|
|
assert_string_equal(pl->to_resource, "res]with|special");
|
|
|
|
|
assert_string_equal(pl->message, "msg with special to");
|
|
|
|
|
ff_parsed_line_free(pl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
test_ff_roundtrip_multiple_lines(void** state)
|
|
|
|
|
{
|
|
|
|
|
/* Write several lines, read and parse them all sequentially */
|
|
|
|
|
char tmppath[] = "/tmp/proftest_multi_XXXXXX";
|
|
|
|
|
int fd = mkstemp(tmppath);
|
|
|
|
|
assert_true(fd >= 0);
|
|
|
|
|
|
|
|
|
|
FILE* fp = fdopen(fd, "w");
|
|
|
|
|
assert_non_null(fp);
|
|
|
|
|
|
|
|
|
|
ff_write_line(fp, "2025-01-01T00:00:01Z", "chat", "none",
|
|
|
|
|
NULL, NULL, NULL, "a@b.com", NULL,
|
|
|
|
|
NULL, NULL, -1, "first");
|
|
|
|
|
ff_write_line(fp, "2025-01-01T00:00:02Z", "chat", "none",
|
|
|
|
|
NULL, NULL, NULL, "c@d.com", NULL,
|
|
|
|
|
NULL, NULL, -1, "second");
|
|
|
|
|
ff_write_line(fp, "2025-01-01T00:00:03Z", "muc", "omemo",
|
|
|
|
|
"sid-3", NULL, NULL, "room@conf.com", "nick",
|
|
|
|
|
NULL, NULL, -1, "third");
|
|
|
|
|
fclose(fp);
|
|
|
|
|
|
|
|
|
|
fp = fopen(tmppath, "r");
|
|
|
|
|
assert_non_null(fp);
|
|
|
|
|
|
|
|
|
|
int count = 0;
|
|
|
|
|
const char* expected_msgs[] = { "first", "second", "third" };
|
|
|
|
|
const char* expected_from[] = { "a@b.com", "c@d.com", "room@conf.com" };
|
|
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
|
gboolean truncated = FALSE;
|
|
|
|
|
char* buf = ff_readline(fp, &truncated);
|
|
|
|
|
if (!buf)
|
|
|
|
|
break;
|
|
|
|
|
ff_parsed_line_t* pl = ff_parse_line(buf);
|
|
|
|
|
free(buf);
|
|
|
|
|
if (pl) {
|
|
|
|
|
assert_true(count < 3);
|
|
|
|
|
assert_string_equal(pl->message, expected_msgs[count]);
|
|
|
|
|
assert_string_equal(pl->from_jid, expected_from[count]);
|
|
|
|
|
ff_parsed_line_free(pl);
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fclose(fp);
|
|
|
|
|
unlink(tmppath);
|
|
|
|
|
|
|
|
|
|
assert_int_equal(count, 3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ================================================================
|
|
|
|
|
* Additional parser edge-case tests
|
|
|
|
|
* ================================================================ */
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
test_ff_parsed_line_free_null_safe(void** state)
|
|
|
|
|
{
|
|
|
|
|
/* ff_parsed_line_free(NULL) must not crash */
|
|
|
|
|
ff_parsed_line_free(NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
test_ff_parse_line_no_space_rejected(void** state)
|
|
|
|
|
{
|
|
|
|
|
/* A line with no spaces at all cannot be parsed */
|
|
|
|
|
assert_null(ff_parse_line("noseparatoratall"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
test_ff_parse_line_unclosed_bracket(void** state)
|
|
|
|
|
{
|
|
|
|
|
/* Unclosed metadata bracket should return NULL */
|
|
|
|
|
assert_null(ff_parse_line("2025-03-01T10:00:00Z [chat|none a@b.com: msg"));
|
|
|
|
|
}
|
|
|
|
|
|