-module(operations_test). -include("operations.hrl"). -include_lib("eunit/include/eunit.hrl"). %% Test fixtures and setup setup_test_env() -> operations:setup(), ok. cleanup_test_env() -> mnesia:stop(), ok. %% Fixture: Create a sample database with a users relation sample_database() -> DB = operations:create_database(test_db), {DB1, _Relation} = operations:create_relation(DB, users, #{ name => string, age => integer }), DB1. %% Fixture: Add sample tuples to a database sample_database_with_tuples() -> DB = sample_database(), {DB1, _} = operations:create_tuple(DB, users, #{name => "Alice", age => 30}), {DB2, _} = operations:create_tuple(DB1, users, #{name => "Bob", age => 25}), DB2. %%==================================================================== %% Database Creation Tests %%==================================================================== database_creation_test_() -> {setup, fun setup_test_env/0, fun(_) -> cleanup_test_env() end, [?_test(begin DB = operations:create_database(my_db), ?assertEqual(my_db, DB#database_state.name), ?assertEqual(#{}, DB#database_state.relations), ?assertNotEqual(undefined, DB#database_state.timestamp) end)]}. %%==================================================================== %% Relation Creation Tests %%==================================================================== relation_creation_test_() -> {setup, fun setup_test_env/0, fun(_) -> cleanup_test_env() end, [?_test(begin DB = operations:create_database(test_db), Schema = #{name => string, email => string}, {DB1, Relation} = operations:create_relation(DB, users, Schema), % Verify relation was created ?assertEqual(users, Relation#relation.name), ?assertEqual(Schema, Relation#relation.schema), % Verify database was updated ?assertEqual([users], operations:get_relations(DB1)), {ok, RelationHash} = operations:get_relation_hash(DB1, users), ?assertEqual(Relation#relation.hash, RelationHash) end)]}. %%==================================================================== %% Tuple Operations Tests %%==================================================================== tuple_creation_test_() -> {setup, fun setup_test_env/0, fun(_) -> cleanup_test_env() end, [?_test(begin DB = sample_database(), InitialHash = DB#database_state.hash, % Add a tuple {DB1, _} = operations:create_tuple(DB, users, #{name => "Alice", age => 30}), % Verify database hash changed ?assertNotEqual(InitialHash, DB1#database_state.hash), % Verify tuple can be retrieved Iterator = operations:get_tuples_iterator(DB1, users), Tuples = operations:collect_all(Iterator), ?assertEqual(1, length(Tuples)), [Tuple] = Tuples, ?assertEqual("Alice", maps:get(name, Tuple)), ?assertEqual(30, maps:get(age, Tuple)) end)]}. tuple_multiple_insertions_test_() -> {setup, fun setup_test_env/0, fun(_) -> cleanup_test_env() end, [?_test(begin DB = sample_database(), % Add multiple tuples {DB1, _} = operations:create_tuple(DB, users, #{name => "Alice", age => 30}), {DB2, _} = operations:create_tuple(DB1, users, #{name => "Bob", age => 25}), {DB3, _} = operations:create_tuple(DB2, users, #{name => "Charlie", age => 35}), % Verify all tuples can be retrieved Iterator = operations:get_tuples_iterator(DB3, users), Tuples = operations:collect_all(Iterator), ?assertEqual(3, length(Tuples)), % Verify hashes changed with each insertion ?assertNotEqual(DB#database_state.hash, DB1#database_state.hash), ?assertNotEqual(DB1#database_state.hash, DB2#database_state.hash), ?assertNotEqual(DB2#database_state.hash, DB3#database_state.hash) end)]}. %%==================================================================== %% Hash Comparison Tests (Time-based) %%==================================================================== database_state_hash_comparison_test_() -> {setup, fun setup_test_env/0, fun(_) -> cleanup_test_env() end, [?_test(begin DB = sample_database(), Hash0 = DB#database_state.hash, % State 1: Add first tuple {DB1, _} = operations:create_tuple(DB, users, #{name => "Alice", age => 30}), Hash1 = DB1#database_state.hash, % State 2: Add second tuple {DB2, _} = operations:create_tuple(DB1, users, #{name => "Bob", age => 25}), Hash2 = DB2#database_state.hash, % State 3: Add third tuple {DB3, _} = operations:create_tuple(DB2, users, #{name => "Charlie", age => 35}), Hash3 = DB3#database_state.hash, % Verify all hashes are unique ?assertNotEqual(Hash0, Hash1), ?assertNotEqual(Hash1, Hash2), ?assertNotEqual(Hash2, Hash3), ?assertNotEqual(Hash0, Hash2), ?assertNotEqual(Hash0, Hash3), ?assertNotEqual(Hash1, Hash3) end)]}. relation_hash_comparison_test_() -> {setup, fun setup_test_env/0, fun(_) -> cleanup_test_env() end, [?_test(begin DB = sample_database(), {ok, RelationHash0} = operations:get_relation_hash(DB, users), % Add tuple and get new relation hash {DB1, Relation1} = operations:create_tuple(DB, users, #{name => "Alice", age => 30}), RelationHash1 = Relation1#relation.hash, % Add another tuple {DB2, Relation2} = operations:create_tuple(DB1, users, #{name => "Bob", age => 25}), RelationHash2 = Relation2#relation.hash, % Verify relation hashes changed over time ?assertNotEqual(RelationHash0, RelationHash1), ?assertNotEqual(RelationHash1, RelationHash2), ?assertNotEqual(RelationHash0, RelationHash2), % Verify we can still get relation hash from database {ok, CurrentHash} = operations:get_relation_hash(DB2, users), ?assertEqual(RelationHash2, CurrentHash) end)]}. %%==================================================================== %% Tuple Retraction Tests %%==================================================================== retract_tuple_test_() -> {setup, fun setup_test_env/0, fun(_) -> cleanup_test_env() end, [?_test(begin DB = sample_database_with_tuples(), {ok, RelationHash} = operations:get_relation_hash(DB, users), % Get tuple hashes TupleHashes = operations:hashes_from_tuple(RelationHash), ?assertEqual(2, length(TupleHashes)), % Retract one tuple [TupleHash | _] = TupleHashes, {atomic, {DB1, _}} = operations:retract_tuple(DB, RelationHash, TupleHash), % Verify tuple was removed {ok, NewRelationHash} = operations:get_relation_hash(DB1, users), NewTupleHashes = operations:hashes_from_tuple(NewRelationHash), ?assertEqual(1, length(NewTupleHashes)), % Verify database hash changed ?assertNotEqual(DB#database_state.hash, DB1#database_state.hash) end)]}. %%==================================================================== %% Relation Operations Tests %%==================================================================== clear_relation_test_() -> {setup, fun setup_test_env/0, fun(_) -> cleanup_test_env() end, [?_test(begin DB = sample_database_with_tuples(), {ok, RelationHash} = operations:get_relation_hash(DB, users), % Verify tuples exist TupleHashes = operations:hashes_from_tuple(RelationHash), ?assertEqual(2, length(TupleHashes)), % Clear relation {atomic, {DB1, ClearedRelation}} = operations:clear_relation(DB, RelationHash), % Verify relation is empty ?assertEqual(undefined, ClearedRelation#relation.tree), NewTupleHashes = operations:hashes_from_tuple(ClearedRelation#relation.hash), ?assertEqual([], NewTupleHashes), % Verify database hash changed ?assertNotEqual(DB#database_state.hash, DB1#database_state.hash) end)]}. retract_relation_test_() -> {setup, fun setup_test_env/0, fun(_) -> cleanup_test_env() end, [?_test(begin DB = sample_database_with_tuples(), % Verify relation exists ?assertEqual([users], operations:get_relations(DB)), % Retract relation {atomic, DB1} = operations:retract_relation(DB, users), % Verify relation was removed ?assertEqual([], operations:get_relations(DB1)), ?assertEqual({error, not_found}, operations:get_relation_hash(DB1, users)), % Verify database hash changed ?assertNotEqual(DB#database_state.hash, DB1#database_state.hash) end)]}. %%==================================================================== %% Iterator Tests %%==================================================================== iterator_basic_test_() -> {setup, fun setup_test_env/0, fun(_) -> cleanup_test_env() end, [?_test(begin DB = sample_database_with_tuples(), % Get iterator and retrieve tuples one by one Iterator = operations:get_tuples_iterator(DB, users), % Get first tuple {ok, Tuple1} = operations:next_tuple(Iterator), ?assert(is_map(Tuple1)), ?assert(maps:is_key(name, Tuple1)), ?assert(maps:is_key(age, Tuple1)), % Get second tuple {ok, Tuple2} = operations:next_tuple(Iterator), ?assert(is_map(Tuple2)), % No more tuples ?assertEqual(done, operations:next_tuple(Iterator)), operations:close_iterator(Iterator) end)]}. iterator_collect_all_test_() -> {setup, fun setup_test_env/0, fun(_) -> cleanup_test_env() end, [?_test(begin DB = sample_database_with_tuples(), Iterator = operations:get_tuples_iterator(DB, users), Tuples = operations:collect_all(Iterator), ?assertEqual(2, length(Tuples)), % Verify both tuples are present Names = [maps:get(name, T) || T <- Tuples], ?assert(lists:member("Alice", Names)), ?assert(lists:member("Bob", Names)) end)]}. iterator_empty_relation_test_() -> {setup, fun setup_test_env/0, fun(_) -> cleanup_test_env() end, [?_test(begin DB = sample_database(), Iterator = operations:get_tuples_iterator(DB, users), Tuples = operations:collect_all(Iterator), ?assertEqual([], Tuples) end)]}. %%==================================================================== %% Hash Consistency Tests %%==================================================================== hash_determinism_test_() -> {setup, fun setup_test_env/0, fun(_) -> cleanup_test_env() end, [?_test(begin % Same value should produce same hash Hash1 = operations:hash(<<"test">>), Hash2 = operations:hash(<<"test">>), ?assertEqual(Hash1, Hash2), % Different values should produce different hashes Hash3 = operations:hash(<<"different">>), ?assertNotEqual(Hash1, Hash3) end)]}. database_state_consistency_test_() -> {setup, fun setup_test_env/0, fun(_) -> cleanup_test_env() end, [?_test(begin % Create two identical databases with same operations DB1 = operations:create_database(db1), {DB1_1, _} = operations:create_relation(DB1, users, #{name => string}), {DB1_2, _} = operations:create_tuple(DB1_1, users, #{name => "Alice"}), DB2 = operations:create_database(db2), {DB2_1, _} = operations:create_relation(DB2, users, #{name => string}), {DB2_2, _} = operations:create_tuple(DB2_1, users, #{name => "Alice"}), % Relations should have the same hash (same schema and tuples) {ok, RelHash1} = operations:get_relation_hash(DB1_2, users), {ok, RelHash2} = operations:get_relation_hash(DB2_2, users), ?assertEqual(RelHash1, RelHash2) end)]}.