asfur/test_network.c
Lorenzo Torres 238eff5bb3 Add unit test suite and complete protocol documentation
- Add test framework (test.h) with assertion macros and test runner
- Add comprehensive unit tests (test_network.c) covering:
  - Packet parsing and error handling
  - User registration and authentication
  - Room operations (create, join, leave, delete, list)
  - Direct messaging functionality
  - Message broadcasting
  - Password hashing
- Update Makefile with 'make test' target
- Rewrite PROTOCOL file with complete specification:
  - All 14 packet types with data layouts
  - All error codes with descriptions
  - Typical usage flows
2026-01-08 15:51:01 +01:00

1117 lines
35 KiB
C

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#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(&reg, 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, &reg, 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(&reg, 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, &reg, 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(&reg, 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, &reg, 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(&reg, 0, sizeof(reg));
strncpy(reg.username, "validuser", sizeof(reg.username));
/* Empty password */
char buf[256];
size_t len = build_packet(buf, PACKET_REGISTER, &reg, 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(&reg, 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, &reg, 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(&reg, 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, &reg, 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(&reg, 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, &reg, 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();
}