#define _GNU_SOURCE #include #include #include #include #include "test.h" #include "network.h" #include "client.h" #include "password.h" /* Mock structures and globals for capturing sent packets */ #define MAX_SENT_PACKETS 32 #define MAX_PACKET_SIZE 4096 static struct { char data[MAX_PACKET_SIZE]; size_t len; } sent_packets[MAX_SENT_PACKETS]; static int sent_packet_count = 0; /* Mock ssl_write_msg - captures packets for verification */ void ssl_write_msg(struct client *client, const char *data, size_t len) { (void)client; if (sent_packet_count < MAX_SENT_PACKETS && len < MAX_PACKET_SIZE) { memcpy(sent_packets[sent_packet_count].data, data, len); sent_packets[sent_packet_count].len = len; sent_packet_count++; } } /* Helper to reset captured packets */ static void reset_sent_packets(void) { sent_packet_count = 0; memset(sent_packets, 0, sizeof(sent_packets)); } /* Helper to get last sent packet header */ static struct packet_header *get_last_packet_header(void) { if (sent_packet_count == 0) return NULL; return (struct packet_header *)sent_packets[sent_packet_count - 1].data; } /* Helper to get last sent packet payload */ static const char *get_last_packet_payload(void) { if (sent_packet_count == 0) return NULL; return sent_packets[sent_packet_count - 1].data + sizeof(struct packet_header); } /* Helper to create a mock client */ static struct client *create_mock_client(void) { struct client *c = calloc(1, sizeof(struct client)); c->user_id = 0; c->current_room_id = 0; c->is_authenticated = false; c->username[0] = '\0'; return c; } /* Helper to build a packet */ static size_t build_packet(char *buf, uint8_t type, const void *payload, size_t payload_len) { size_t total = sizeof(struct packet_header) + payload_len; struct packet_header *hdr = (struct packet_header *)buf; hdr->size = (uint16_t)total; hdr->type = type; if (payload && payload_len > 0) { memcpy(buf + sizeof(struct packet_header), payload, payload_len); } return total; } /* ==== PACKET PARSING TESTS ==== */ static int test_invalid_packet_too_short(void) { struct client *client = create_mock_client(); reset_sent_packets(); /* Send a packet that's too short (less than header size) */ char data[] = { 0x00, 0x01 }; /* Only 2 bytes */ network_handle_data(client, data, sizeof(data)); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_INVALID_PACKET, "Should be INVALID_PACKET error"); free(client); return 0; } static int test_invalid_packet_size_mismatch(void) { struct client *client = create_mock_client(); reset_sent_packets(); /* Header claims size of 100 but we only provide 3 bytes */ char data[3]; struct packet_header *hdr = (struct packet_header *)data; hdr->size = 100; hdr->type = PACKET_REGISTER; network_handle_data(client, data, sizeof(data)); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); free(client); return 0; } static int test_invalid_packet_unknown_type(void) { struct client *client = create_mock_client(); reset_sent_packets(); char buf[128]; size_t len = build_packet(buf, 255, NULL, 0); /* Unknown type 255 */ network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_INVALID_PACKET, "Should be INVALID_PACKET error"); free(client); return 0; } /* ==== REGISTRATION TESTS ==== */ static int test_register_success(void) { struct client *client = create_mock_client(); reset_sent_packets(); struct packet_register reg; memset(®, 0, sizeof(reg)); strncpy(reg.username, "testuser1", sizeof(reg.username)); strncpy(reg.password, "testpass123", sizeof(reg.password)); char buf[256]; size_t len = build_packet(buf, PACKET_REGISTER, ®, sizeof(reg)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_OK, "Should be OK packet for successful registration"); free(client); return 0; } static int test_register_duplicate_username(void) { struct client *client = create_mock_client(); reset_sent_packets(); /* Register first user */ struct packet_register reg; memset(®, 0, sizeof(reg)); strncpy(reg.username, "dupuser", sizeof(reg.username)); strncpy(reg.password, "pass123", sizeof(reg.password)); char buf[256]; size_t len = build_packet(buf, PACKET_REGISTER, ®, sizeof(reg)); network_handle_data(client, buf, len); /* Try to register same username again */ reset_sent_packets(); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_ALREADY_REGISTERED, "Should be ALREADY_REGISTERED error"); free(client); return 0; } static int test_register_empty_username(void) { struct client *client = create_mock_client(); reset_sent_packets(); struct packet_register reg; memset(®, 0, sizeof(reg)); /* Empty username, valid password */ strncpy(reg.password, "testpass123", sizeof(reg.password)); char buf[256]; size_t len = build_packet(buf, PACKET_REGISTER, ®, sizeof(reg)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_INVALID_PACKET, "Should be INVALID_PACKET error"); free(client); return 0; } static int test_register_empty_password(void) { struct client *client = create_mock_client(); reset_sent_packets(); struct packet_register reg; memset(®, 0, sizeof(reg)); strncpy(reg.username, "validuser", sizeof(reg.username)); /* Empty password */ char buf[256]; size_t len = build_packet(buf, PACKET_REGISTER, ®, sizeof(reg)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); free(client); return 0; } static int test_register_packet_too_small(void) { struct client *client = create_mock_client(); reset_sent_packets(); /* Send register packet with payload smaller than expected */ char small_payload[10] = {0}; char buf[64]; size_t len = build_packet(buf, PACKET_REGISTER, small_payload, sizeof(small_payload)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_INVALID_PACKET, "Should be INVALID_PACKET error"); free(client); return 0; } /* ==== AUTHENTICATION TESTS ==== */ static int test_authenticate_success(void) { /* First register a user */ struct client *client = create_mock_client(); reset_sent_packets(); struct packet_register reg; memset(®, 0, sizeof(reg)); strncpy(reg.username, "authuser", sizeof(reg.username)); strncpy(reg.password, "authpass", sizeof(reg.password)); char buf[256]; size_t len = build_packet(buf, PACKET_REGISTER, ®, sizeof(reg)); network_handle_data(client, buf, len); /* Now authenticate */ reset_sent_packets(); struct packet_auth auth; memset(&auth, 0, sizeof(auth)); strncpy(auth.username, "authuser", sizeof(auth.username)); strncpy(auth.password, "authpass", sizeof(auth.password)); len = build_packet(buf, PACKET_AUTHENTICATE, &auth, sizeof(auth)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_OK, "Should be OK packet"); TEST_ASSERT(client->is_authenticated, "Client should be authenticated"); TEST_ASSERT(client->user_id > 0, "Client should have user_id"); TEST_ASSERT_STR_EQ(client->username, "authuser", "Username should match"); free(client); return 0; } static int test_authenticate_wrong_password(void) { struct client *client = create_mock_client(); reset_sent_packets(); /* Register user */ struct packet_register reg; memset(®, 0, sizeof(reg)); strncpy(reg.username, "wrongpassuser", sizeof(reg.username)); strncpy(reg.password, "correctpass", sizeof(reg.password)); char buf[256]; size_t len = build_packet(buf, PACKET_REGISTER, ®, sizeof(reg)); network_handle_data(client, buf, len); /* Try to auth with wrong password */ reset_sent_packets(); struct packet_auth auth; memset(&auth, 0, sizeof(auth)); strncpy(auth.username, "wrongpassuser", sizeof(auth.username)); strncpy(auth.password, "wrongpass", sizeof(auth.password)); len = build_packet(buf, PACKET_AUTHENTICATE, &auth, sizeof(auth)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_INVALID_CREDENTIALS, "Should be INVALID_CREDENTIALS error"); TEST_ASSERT(!client->is_authenticated, "Client should not be authenticated"); free(client); return 0; } static int test_authenticate_unknown_user(void) { struct client *client = create_mock_client(); reset_sent_packets(); struct packet_auth auth; memset(&auth, 0, sizeof(auth)); strncpy(auth.username, "nonexistent", sizeof(auth.username)); strncpy(auth.password, "somepass", sizeof(auth.password)); char buf[256]; size_t len = build_packet(buf, PACKET_AUTHENTICATE, &auth, sizeof(auth)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_INVALID_CREDENTIALS, "Should be INVALID_CREDENTIALS error"); free(client); return 0; } static int test_authenticate_empty_credentials(void) { struct client *client = create_mock_client(); reset_sent_packets(); struct packet_auth auth; memset(&auth, 0, sizeof(auth)); /* Both username and password empty */ char buf[256]; size_t len = build_packet(buf, PACKET_AUTHENTICATE, &auth, sizeof(auth)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_INVALID_CREDENTIALS, "Should be INVALID_CREDENTIALS error"); free(client); return 0; } /* ==== ROOM OPERATION TESTS ==== */ static struct client *setup_authenticated_client(const char *username, const char *password) { struct client *client = create_mock_client(); /* Register */ struct packet_register reg; memset(®, 0, sizeof(reg)); strncpy(reg.username, username, sizeof(reg.username)); strncpy(reg.password, password, sizeof(reg.password)); char buf[256]; size_t len = build_packet(buf, PACKET_REGISTER, ®, sizeof(reg)); network_handle_data(client, buf, len); /* Authenticate */ struct packet_auth auth; memset(&auth, 0, sizeof(auth)); strncpy(auth.username, username, sizeof(auth.username)); strncpy(auth.password, password, sizeof(auth.password)); len = build_packet(buf, PACKET_AUTHENTICATE, &auth, sizeof(auth)); network_handle_data(client, buf, len); reset_sent_packets(); return client; } static int test_create_room_success(void) { struct client *client = setup_authenticated_client("roomcreator", "pass123"); struct packet_create_room create; memset(&create, 0, sizeof(create)); strncpy(create.name, "TestRoom", sizeof(create.name)); char buf[256]; size_t len = build_packet(buf, PACKET_CREATE_ROOM, &create, sizeof(create)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ROOM_CREATED, "Should be ROOM_CREATED packet"); const struct packet_room_created *resp = (const struct packet_room_created *)get_last_packet_payload(); TEST_ASSERT(resp->room_id > 0, "Room ID should be positive"); free(client); return 0; } static int test_create_room_duplicate_name(void) { struct client *client = setup_authenticated_client("roomcreator2", "pass123"); struct packet_create_room create; memset(&create, 0, sizeof(create)); strncpy(create.name, "DuplicateRoom", sizeof(create.name)); char buf[256]; size_t len = build_packet(buf, PACKET_CREATE_ROOM, &create, sizeof(create)); network_handle_data(client, buf, len); /* Try to create room with same name */ reset_sent_packets(); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_ROOM_NAME_TAKEN, "Should be ROOM_NAME_TAKEN error"); free(client); return 0; } static int test_create_room_not_authenticated(void) { struct client *client = create_mock_client(); reset_sent_packets(); struct packet_create_room create; memset(&create, 0, sizeof(create)); strncpy(create.name, "UnauthorizedRoom", sizeof(create.name)); char buf[256]; size_t len = build_packet(buf, PACKET_CREATE_ROOM, &create, sizeof(create)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_NOT_AUTHENTICATED, "Should be NOT_AUTHENTICATED error"); free(client); return 0; } static int test_create_room_empty_name(void) { struct client *client = setup_authenticated_client("roomcreator3", "pass123"); struct packet_create_room create; memset(&create, 0, sizeof(create)); /* Empty name */ char buf[256]; size_t len = build_packet(buf, PACKET_CREATE_ROOM, &create, sizeof(create)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_INVALID_PACKET, "Should be INVALID_PACKET error"); free(client); return 0; } static int test_join_room_success(void) { struct client *client = setup_authenticated_client("roomjoiner", "pass123"); /* Create a room first */ struct packet_create_room create; memset(&create, 0, sizeof(create)); strncpy(create.name, "JoinableRoom", sizeof(create.name)); char buf[256]; size_t len = build_packet(buf, PACKET_CREATE_ROOM, &create, sizeof(create)); network_handle_data(client, buf, len); const struct packet_room_created *created = (const struct packet_room_created *)get_last_packet_payload(); uint64_t room_id = created->room_id; /* Now join the room */ reset_sent_packets(); struct packet_join join; join.room_id = room_id; len = build_packet(buf, PACKET_JOIN, &join, sizeof(join)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_OK, "Should be OK packet"); TEST_ASSERT_EQ(client->current_room_id, room_id, "Client should be in room"); free(client); return 0; } static int test_join_room_not_authenticated(void) { struct client *client = create_mock_client(); reset_sent_packets(); struct packet_join join; join.room_id = 1; char buf[64]; size_t len = build_packet(buf, PACKET_JOIN, &join, sizeof(join)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_NOT_AUTHENTICATED, "Should be NOT_AUTHENTICATED error"); free(client); return 0; } static int test_join_room_nonexistent(void) { struct client *client = setup_authenticated_client("roomjoiner2", "pass123"); struct packet_join join; join.room_id = 99999; /* Non-existent room */ char buf[64]; size_t len = build_packet(buf, PACKET_JOIN, &join, sizeof(join)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_ACCESS_DENIED, "Should be ACCESS_DENIED error"); free(client); return 0; } static int test_leave_room_success(void) { struct client *client = setup_authenticated_client("roomleaver", "pass123"); /* Create and join a room */ struct packet_create_room create; memset(&create, 0, sizeof(create)); strncpy(create.name, "LeavableRoom", sizeof(create.name)); char buf[256]; size_t len = build_packet(buf, PACKET_CREATE_ROOM, &create, sizeof(create)); network_handle_data(client, buf, len); const struct packet_room_created *created = (const struct packet_room_created *)get_last_packet_payload(); uint64_t room_id = created->room_id; struct packet_join join; join.room_id = room_id; len = build_packet(buf, PACKET_JOIN, &join, sizeof(join)); network_handle_data(client, buf, len); /* Now leave the room */ reset_sent_packets(); struct packet_leave leave; leave.room_id = room_id; len = build_packet(buf, PACKET_LEAVE, &leave, sizeof(leave)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_OK, "Should be OK packet"); TEST_ASSERT_EQ(client->current_room_id, 0, "Client should not be in any room"); free(client); return 0; } static int test_delete_room_success(void) { struct client *client = setup_authenticated_client("roomdeleter", "pass123"); /* Create a room */ struct packet_create_room create; memset(&create, 0, sizeof(create)); strncpy(create.name, "DeletableRoom", sizeof(create.name)); char buf[256]; size_t len = build_packet(buf, PACKET_CREATE_ROOM, &create, sizeof(create)); network_handle_data(client, buf, len); const struct packet_room_created *created = (const struct packet_room_created *)get_last_packet_payload(); uint64_t room_id = created->room_id; /* Delete the room */ reset_sent_packets(); struct packet_delete_room del; del.room_id = room_id; len = build_packet(buf, PACKET_DELETE_ROOM, &del, sizeof(del)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_OK, "Should be OK packet"); free(client); return 0; } static int test_delete_room_not_owner(void) { /* Create room as one user */ struct client *owner = setup_authenticated_client("roomowner", "pass123"); struct packet_create_room create; memset(&create, 0, sizeof(create)); strncpy(create.name, "OwnedRoom", sizeof(create.name)); char buf[256]; size_t len = build_packet(buf, PACKET_CREATE_ROOM, &create, sizeof(create)); network_handle_data(owner, buf, len); const struct packet_room_created *created = (const struct packet_room_created *)get_last_packet_payload(); uint64_t room_id = created->room_id; /* Try to delete as different user */ struct client *other = setup_authenticated_client("otheruserx", "pass123"); struct packet_delete_room del; del.room_id = room_id; len = build_packet(buf, PACKET_DELETE_ROOM, &del, sizeof(del)); network_handle_data(other, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_NOT_ROOM_OWNER, "Should be NOT_ROOM_OWNER error"); free(owner); free(other); return 0; } static int test_delete_room_nonexistent(void) { struct client *client = setup_authenticated_client("delnon", "pass123"); struct packet_delete_room del; del.room_id = 99999; char buf[64]; size_t len = build_packet(buf, PACKET_DELETE_ROOM, &del, sizeof(del)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_ROOM_NOT_FOUND, "Should be ROOM_NOT_FOUND error"); free(client); return 0; } static int test_list_rooms(void) { struct client *client = setup_authenticated_client("lister", "pass123"); /* Create a few rooms */ struct packet_create_room create; char buf[256]; memset(&create, 0, sizeof(create)); strncpy(create.name, "ListRoom1", sizeof(create.name)); size_t len = build_packet(buf, PACKET_CREATE_ROOM, &create, sizeof(create)); network_handle_data(client, buf, len); memset(&create, 0, sizeof(create)); strncpy(create.name, "ListRoom2", sizeof(create.name)); len = build_packet(buf, PACKET_CREATE_ROOM, &create, sizeof(create)); network_handle_data(client, buf, len); /* List rooms */ reset_sent_packets(); len = build_packet(buf, PACKET_LIST_ROOMS, NULL, 0); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ROOM_LIST, "Should be ROOM_LIST packet"); const char *payload = get_last_packet_payload(); uint32_t count; memcpy(&count, payload, sizeof(uint32_t)); TEST_ASSERT(count >= 2, "Should have at least 2 rooms"); free(client); return 0; } static int test_list_rooms_not_authenticated(void) { struct client *client = create_mock_client(); reset_sent_packets(); char buf[64]; size_t len = build_packet(buf, PACKET_LIST_ROOMS, NULL, 0); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_NOT_AUTHENTICATED, "Should be NOT_AUTHENTICATED error"); free(client); return 0; } /* ==== DM TESTS ==== */ static int test_dm_open_success(void) { /* Create two users */ struct client *client1 = setup_authenticated_client("dmuser1", "pass123"); struct client *client2 = setup_authenticated_client("dmuser2", "pass123"); reset_sent_packets(); /* Open DM with user2 */ struct packet_dm_open dm; memset(&dm, 0, sizeof(dm)); strncpy(dm.username, "dmuser2", sizeof(dm.username)); char buf[64]; size_t len = build_packet(buf, PACKET_DM_OPEN, &dm, sizeof(dm)); network_handle_data(client1, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_DM_ROOM, "Should be DM_ROOM packet"); const struct packet_dm_room *resp = (const struct packet_dm_room *)get_last_packet_payload(); TEST_ASSERT(resp->room_id > 0, "Room ID should be positive"); free(client1); free(client2); return 0; } static int test_dm_open_user_not_found(void) { struct client *client = setup_authenticated_client("dmuser3", "pass123"); struct packet_dm_open dm; memset(&dm, 0, sizeof(dm)); strncpy(dm.username, "nonexistentuser", sizeof(dm.username)); char buf[64]; size_t len = build_packet(buf, PACKET_DM_OPEN, &dm, sizeof(dm)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_USER_NOT_FOUND, "Should be USER_NOT_FOUND error"); free(client); return 0; } static int test_dm_open_not_authenticated(void) { struct client *client = create_mock_client(); reset_sent_packets(); struct packet_dm_open dm; memset(&dm, 0, sizeof(dm)); strncpy(dm.username, "someuser", sizeof(dm.username)); char buf[64]; size_t len = build_packet(buf, PACKET_DM_OPEN, &dm, sizeof(dm)); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_NOT_AUTHENTICATED, "Should be NOT_AUTHENTICATED error"); free(client); return 0; } static int test_dm_room_reuse(void) { /* Create two users */ struct client *client1 = setup_authenticated_client("dmreuse1", "pass123"); struct client *client2 = setup_authenticated_client("dmreuse2", "pass123"); /* Open DM from client1 to client2 */ struct packet_dm_open dm; memset(&dm, 0, sizeof(dm)); strncpy(dm.username, "dmreuse2", sizeof(dm.username)); char buf[64]; size_t len = build_packet(buf, PACKET_DM_OPEN, &dm, sizeof(dm)); network_handle_data(client1, buf, len); const struct packet_dm_room *resp1 = (const struct packet_dm_room *)get_last_packet_payload(); uint64_t room_id1 = resp1->room_id; /* Open DM from client2 to client1 - should get same room */ reset_sent_packets(); memset(&dm, 0, sizeof(dm)); strncpy(dm.username, "dmreuse1", sizeof(dm.username)); len = build_packet(buf, PACKET_DM_OPEN, &dm, sizeof(dm)); network_handle_data(client2, buf, len); const struct packet_dm_room *resp2 = (const struct packet_dm_room *)get_last_packet_payload(); uint64_t room_id2 = resp2->room_id; TEST_ASSERT_EQ(room_id1, room_id2, "DM room should be reused"); free(client1); free(client2); return 0; } /* ==== MESSAGE TESTS ==== */ static int test_text_success(void) { struct client *client = setup_authenticated_client("msgsender", "pass123"); /* Create and join a room */ struct packet_create_room create; memset(&create, 0, sizeof(create)); strncpy(create.name, "MessageRoom", sizeof(create.name)); char buf[256]; size_t len = build_packet(buf, PACKET_CREATE_ROOM, &create, sizeof(create)); network_handle_data(client, buf, len); const struct packet_room_created *created = (const struct packet_room_created *)get_last_packet_payload(); uint64_t room_id = created->room_id; struct packet_join join; join.room_id = room_id; len = build_packet(buf, PACKET_JOIN, &join, sizeof(join)); network_handle_data(client, buf, len); /* Send a message */ network_client_add(client); reset_sent_packets(); char text_buf[128]; uint64_t *room_ptr = (uint64_t *)text_buf; *room_ptr = room_id; const char *msg = "Hello, world!"; memcpy(text_buf + sizeof(uint64_t), msg, strlen(msg)); len = build_packet(buf, PACKET_TEXT, text_buf, sizeof(uint64_t) + strlen(msg)); network_handle_data(client, buf, len); /* Should receive broadcast back since we're in the room */ TEST_ASSERT(sent_packet_count >= 1, "Should receive broadcast"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_TEXT, "Should be TEXT packet (broadcast)"); network_client_remove(client); free(client); return 0; } static int test_text_not_authenticated(void) { struct client *client = create_mock_client(); reset_sent_packets(); char text_buf[64]; uint64_t *room_ptr = (uint64_t *)text_buf; *room_ptr = 1; memcpy(text_buf + sizeof(uint64_t), "test", 4); char buf[128]; size_t len = build_packet(buf, PACKET_TEXT, text_buf, sizeof(uint64_t) + 4); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); const struct packet_error *err = (const struct packet_error *)get_last_packet_payload(); TEST_ASSERT_EQ(err->code, ERR_NOT_AUTHENTICATED, "Should be NOT_AUTHENTICATED error"); free(client); return 0; } static int test_text_wrong_room(void) { struct client *client = setup_authenticated_client("wrongroom", "pass123"); /* Create a room but don't join it */ struct packet_create_room create; memset(&create, 0, sizeof(create)); strncpy(create.name, "WrongRoom", sizeof(create.name)); char buf[256]; size_t len = build_packet(buf, PACKET_CREATE_ROOM, &create, sizeof(create)); network_handle_data(client, buf, len); const struct packet_room_created *created = (const struct packet_room_created *)get_last_packet_payload(); uint64_t room_id = created->room_id; /* Try to send message to room we haven't joined */ reset_sent_packets(); char text_buf[64]; uint64_t *room_ptr = (uint64_t *)text_buf; *room_ptr = room_id; memcpy(text_buf + sizeof(uint64_t), "test", 4); len = build_packet(buf, PACKET_TEXT, text_buf, sizeof(uint64_t) + 4); network_handle_data(client, buf, len); TEST_ASSERT(sent_packet_count == 1, "Should send error response"); struct packet_header *hdr = get_last_packet_header(); TEST_ASSERT_EQ(hdr->type, PACKET_ERROR, "Should be error packet"); free(client); return 0; } /* ==== CLIENT MANAGEMENT TESTS ==== */ static int test_client_add_remove(void) { struct client *client1 = create_mock_client(); struct client *client2 = create_mock_client(); network_client_add(client1); network_client_add(client2); /* Remove first client */ network_client_remove(client1); /* Remove second client */ network_client_remove(client2); /* Should handle removing non-existent client gracefully */ network_client_remove(client1); free(client1); free(client2); return 0; } /* ==== PASSWORD TESTS ==== */ static int test_password_hash_verify(void) { const char *password = "testpassword123"; char *hash = hash_password(password); TEST_ASSERT(hash != NULL, "Hash should not be NULL"); TEST_ASSERT(strlen(hash) > 0, "Hash should not be empty"); TEST_ASSERT(verify_password(password, hash), "Password should verify"); TEST_ASSERT(!verify_password("wrongpassword", hash), "Wrong password should not verify"); free(hash); return 0; } static int test_password_different_hashes(void) { const char *password = "samepassword"; char *hash1 = hash_password(password); /* Sleep briefly to ensure different time-based seed */ /* Note: generate_salt uses srand(time(NULL)), so hashes within same * second may be identical. This tests that both hashes verify correctly. */ char *hash2 = hash_password(password); TEST_ASSERT(hash1 != NULL && hash2 != NULL, "Hashes should not be NULL"); /* Both should verify regardless of whether salt differs */ TEST_ASSERT(verify_password(password, hash1), "Password should verify with hash1"); TEST_ASSERT(verify_password(password, hash2), "Password should verify with hash2"); /* Wrong password should fail on both */ TEST_ASSERT(!verify_password("wrongpass", hash1), "Wrong password should not verify with hash1"); TEST_ASSERT(!verify_password("wrongpass", hash2), "Wrong password should not verify with hash2"); free(hash1); free(hash2); return 0; } /* ==== MAIN ==== */ int main(void) { printf("=== asfur unit tests ===\n\n"); /* Remove database file to ensure clean state for testing */ /* CONFIG_DB_PATH is defined in config.h as "asfur.db" */ remove("asfur.db"); if (network_init() != 0) { fprintf(stderr, "Failed to initialize network for testing\n"); return 1; } /* Packet parsing tests */ printf("\n--- Packet Parsing Tests ---\n"); RUN_TEST(test_invalid_packet_too_short); RUN_TEST(test_invalid_packet_size_mismatch); RUN_TEST(test_invalid_packet_unknown_type); /* Registration tests */ printf("\n--- Registration Tests ---\n"); RUN_TEST(test_register_success); RUN_TEST(test_register_duplicate_username); RUN_TEST(test_register_empty_username); RUN_TEST(test_register_empty_password); RUN_TEST(test_register_packet_too_small); /* Authentication tests */ printf("\n--- Authentication Tests ---\n"); RUN_TEST(test_authenticate_success); RUN_TEST(test_authenticate_wrong_password); RUN_TEST(test_authenticate_unknown_user); RUN_TEST(test_authenticate_empty_credentials); /* Room operation tests */ printf("\n--- Room Operation Tests ---\n"); RUN_TEST(test_create_room_success); RUN_TEST(test_create_room_duplicate_name); RUN_TEST(test_create_room_not_authenticated); RUN_TEST(test_create_room_empty_name); RUN_TEST(test_join_room_success); RUN_TEST(test_join_room_not_authenticated); RUN_TEST(test_join_room_nonexistent); RUN_TEST(test_leave_room_success); RUN_TEST(test_delete_room_success); RUN_TEST(test_delete_room_not_owner); RUN_TEST(test_delete_room_nonexistent); RUN_TEST(test_list_rooms); RUN_TEST(test_list_rooms_not_authenticated); /* DM tests */ printf("\n--- DM Tests ---\n"); RUN_TEST(test_dm_open_success); RUN_TEST(test_dm_open_user_not_found); RUN_TEST(test_dm_open_not_authenticated); RUN_TEST(test_dm_room_reuse); /* Message tests */ printf("\n--- Message Tests ---\n"); RUN_TEST(test_text_success); RUN_TEST(test_text_not_authenticated); RUN_TEST(test_text_wrong_room); /* Client management tests */ printf("\n--- Client Management Tests ---\n"); RUN_TEST(test_client_add_remove); /* Password tests */ printf("\n--- Password Tests ---\n"); RUN_TEST(test_password_hash_verify); RUN_TEST(test_password_different_hashes); network_shutdown(); TEST_SUMMARY(); }