#include "test_framework.h"
#include "../src/types.h"
#include "../src/patch.h"
#include <string.h>
void test_patch_has_rules(void) {
TEST_SUITE_BEGIN("Patch Has Rules");
patch_config_t empty_config;
memset(&empty_config, 0, sizeof(empty_config));
empty_config.rule_count = 0;
TEST_ASSERT_EQ(0, patch_has_rules(&empty_config), "Empty config has no rules");
patch_config_t config_with_rules;
memset(&config_with_rules, 0, sizeof(config_with_rules));
config_with_rules.rule_count = 1;
strcpy(config_with_rules.rules[0].key, "test");
config_with_rules.rules[0].key_len = 4;
TEST_ASSERT_EQ(1, patch_has_rules(&config_with_rules), "Config with rules returns true");
TEST_ASSERT_EQ(0, patch_has_rules(NULL), "NULL config has no rules");
TEST_SUITE_END();
}
void test_patch_check_for_block(void) {
TEST_SUITE_BEGIN("Patch Check For Block");
patch_config_t config;
memset(&config, 0, sizeof(config));
config.rule_count = 1;
strcpy(config.rules[0].key, "blocked-word");
config.rules[0].key_len = strlen("blocked-word");
config.rules[0].is_null = 1;
const char *data_with_block = "This content contains blocked-word in it";
TEST_ASSERT_EQ(1, patch_check_for_block(&config, data_with_block, strlen(data_with_block)),
"Content with blocked word is blocked");
const char *data_without_block = "This content is clean and allowed";
TEST_ASSERT_EQ(0, patch_check_for_block(&config, data_without_block, strlen(data_without_block)),
"Clean content is not blocked");
patch_config_t empty_config;
memset(&empty_config, 0, sizeof(empty_config));
TEST_ASSERT_EQ(0, patch_check_for_block(&empty_config, data_with_block, strlen(data_with_block)),
"Empty config blocks nothing");
TEST_ASSERT_EQ(0, patch_check_for_block(NULL, data_with_block, strlen(data_with_block)),
"NULL config blocks nothing");
TEST_ASSERT_EQ(0, patch_check_for_block(&config, NULL, 0),
"NULL data is not blocked");
TEST_SUITE_END();
}
void test_patch_apply_simple_replace(void) {
TEST_SUITE_BEGIN("Patch Apply Simple Replacement");
patch_config_t config;
memset(&config, 0, sizeof(config));
config.rule_count = 1;
strcpy(config.rules[0].key, "old");
config.rules[0].key_len = 3;
strcpy(config.rules[0].value, "new");
config.rules[0].value_len = 3;
config.rules[0].is_null = 0;
const char *input = "This is old text with old words";
char output[256];
patch_result_t result = patch_apply(&config, input, strlen(input), output, sizeof(output));
TEST_ASSERT_EQ(0, result.should_block, "Simple replace does not block");
TEST_ASSERT(result.output_len > 0, "Output has content");
TEST_ASSERT(strstr(output, "new") != NULL, "Replacement word present");
TEST_SUITE_END();
}
void test_patch_apply_size_change(void) {
TEST_SUITE_BEGIN("Patch Apply Size Change");
patch_config_t config;
memset(&config, 0, sizeof(config));
config.rule_count = 1;
strcpy(config.rules[0].key, "short");
config.rules[0].key_len = 5;
strcpy(config.rules[0].value, "much-longer-replacement");
config.rules[0].value_len = strlen("much-longer-replacement");
config.rules[0].is_null = 0;
const char *input = "This is short text";
char output[256];
patch_result_t result = patch_apply(&config, input, strlen(input), output, sizeof(output));
TEST_ASSERT_EQ(0, result.should_block, "Size change replace does not block");
TEST_ASSERT(result.output_len > strlen(input), "Output is longer than input");
TEST_ASSERT(result.size_delta > 0, "Size delta is positive");
TEST_SUITE_END();
}
void test_patch_apply_shrink(void) {
TEST_SUITE_BEGIN("Patch Apply Shrink");
patch_config_t config;
memset(&config, 0, sizeof(config));
config.rule_count = 1;
strcpy(config.rules[0].key, "very-long-word");
config.rules[0].key_len = strlen("very-long-word");
strcpy(config.rules[0].value, "tiny");
config.rules[0].value_len = 4;
config.rules[0].is_null = 0;
const char *input = "This has very-long-word in it";
char output[256];
patch_result_t result = patch_apply(&config, input, strlen(input), output, sizeof(output));
TEST_ASSERT_EQ(0, result.should_block, "Shrink replace does not block");
TEST_ASSERT(result.output_len < strlen(input), "Output is shorter than input");
TEST_ASSERT(result.size_delta < 0, "Size delta is negative");
TEST_SUITE_END();
}
void test_patch_apply_multiple_rules(void) {
TEST_SUITE_BEGIN("Patch Apply Multiple Rules");
patch_config_t config;
memset(&config, 0, sizeof(config));
config.rule_count = 3;
strcpy(config.rules[0].key, "foo");
config.rules[0].key_len = 3;
strcpy(config.rules[0].value, "bar");
config.rules[0].value_len = 3;
config.rules[0].is_null = 0;
strcpy(config.rules[1].key, "hello");
config.rules[1].key_len = 5;
strcpy(config.rules[1].value, "world");
config.rules[1].value_len = 5;
config.rules[1].is_null = 0;
strcpy(config.rules[2].key, "test");
config.rules[2].key_len = 4;
strcpy(config.rules[2].value, "demo");
config.rules[2].value_len = 4;
config.rules[2].is_null = 0;
const char *input = "foo says hello during test";
char output[256];
patch_result_t result = patch_apply(&config, input, strlen(input), output, sizeof(output));
TEST_ASSERT_EQ(0, result.should_block, "Multiple rule replace does not block");
TEST_ASSERT(result.output_len > 0, "Output has content");
output[result.output_len] = '\0';
TEST_ASSERT(strstr(output, "bar") != NULL, "First replacement applied");
TEST_ASSERT(strstr(output, "world") != NULL, "Second replacement applied");
TEST_ASSERT(strstr(output, "demo") != NULL, "Third replacement applied");
TEST_SUITE_END();
}
void test_patch_apply_no_match(void) {
TEST_SUITE_BEGIN("Patch Apply No Match");
patch_config_t config;
memset(&config, 0, sizeof(config));
config.rule_count = 1;
strcpy(config.rules[0].key, "nonexistent");
config.rules[0].key_len = strlen("nonexistent");
strcpy(config.rules[0].value, "replacement");
config.rules[0].value_len = strlen("replacement");
config.rules[0].is_null = 0;
const char *input = "This text has no matching patterns";
char output[256];
patch_result_t result = patch_apply(&config, input, strlen(input), output, sizeof(output));
TEST_ASSERT_EQ(0, result.should_block, "No match does not block");
TEST_ASSERT_EQ(strlen(input), result.output_len, "Output length equals input length");
TEST_ASSERT_EQ(0, result.size_delta, "Size delta is zero for no changes");
TEST_SUITE_END();
}
void test_patch_apply_empty_config(void) {
TEST_SUITE_BEGIN("Patch Apply Empty Config");
patch_config_t config;
memset(&config, 0, sizeof(config));
const char *input = "This is some input data";
char output[256];
patch_result_t result = patch_apply(&config, input, strlen(input), output, sizeof(output));
TEST_ASSERT_EQ(0, result.should_block, "Empty config does not block");
TEST_ASSERT_EQ(strlen(input), result.output_len, "Output equals input for empty config");
TEST_SUITE_END();
}
void test_patch_apply_block_rule(void) {
TEST_SUITE_BEGIN("Patch Apply Block Rule");
patch_config_t config;
memset(&config, 0, sizeof(config));
config.rule_count = 1;
strcpy(config.rules[0].key, "malware");
config.rules[0].key_len = strlen("malware");
config.rules[0].is_null = 1;
const char *malicious = "This contains malware content";
char output[256];
patch_result_t result = patch_apply(&config, malicious, strlen(malicious), output, sizeof(output));
TEST_ASSERT_EQ(1, result.should_block, "Block rule triggers block");
const char *clean = "This is clean content";
result = patch_apply(&config, clean, strlen(clean), output, sizeof(output));
TEST_ASSERT_EQ(0, result.should_block, "Clean content not blocked");
TEST_SUITE_END();
}
void run_patch_tests(void) {
test_patch_has_rules();
test_patch_check_for_block();
test_patch_apply_simple_replace();
test_patch_apply_size_change();
test_patch_apply_shrink();
test_patch_apply_multiple_rules();
test_patch_apply_no_match();
test_patch_apply_empty_config();
test_patch_apply_block_rule();
}