lib/mix/tasks/database/set_up_test_database.ex

defmodule Mix.Tasks.SetUpTestDatabase do
  use Mix.Task

  alias Mix.Tasks.Helpers

  @test_database "todos_test"

  @shortdoc "Set up test database and tables"
  def run(_) do
    Mix.Task.run("app.start")
    setup_test_database()
  end

  defp setup_test_database do
    Helpers.create_database(@test_database)

    {:ok, pid} =
      Postgrex.start_link(
        hostname: "localhost",
        username: "postgres",
        password: "postgres",
        database: @test_database
      )

    create_database_logic(pid)
    create_database_tables(pid)
    seed_database_tables(pid)
    set_up_logical_replication(pid)
  end

  defp create_database_logic(pid) do
    Helpers.create_extension(pid, "citext")
    Helpers.create_extension(pid, "uuid-ossp")
    create_updated_at_function(pid)
  end

  defp create_database_tables(pid) do
    create_user_table(pid)
    create_updated_at_trigger(pid, "user")
    create_todo_table(pid)
    create_updated_at_trigger(pid, "todo")
  end

  defp set_up_logical_replication(pid) do
    set_wal_level_to_logical(pid)
    set_event_publications(pid)
    set_replica_identity(pid, "user")
    set_replica_identity(pid, "todo")
  end

  defp set_wal_level_to_logical(pid) do
    set_wal_level = "ALTER SYSTEM SET wal_level = \'logical\';"

    Postgrex.query!(pid, set_wal_level, [])
  end

  defp set_event_publications(pid) do
    event_publications = "CREATE PUBLICATION events FOR TABLE \"user\", \"todo\";"

    Postgrex.query!(pid, event_publications, [])
  end

  defp set_replica_identity(pid, table_name) do
    replica_identity = "ALTER TABLE \"#{table_name}\" REPLICA IDENTITY FULL;"

    Postgrex.query!(pid, replica_identity, [])
  end

  defp create_updated_at_function(pid) do
    create_updated_at_function_statement = """
      CREATE OR REPLACE FUNCTION set_current_timestamp_updated_at()
      RETURNS TRIGGER AS $$
      DECLARE
        _new record;
      BEGIN
        _new := NEW;
        _new."updated_at" = NOW();
        RETURN _new;
      END;
      $$ LANGUAGE plpgsql;
    """

    Postgrex.query!(pid, create_updated_at_function_statement, [])
  end

  defp create_updated_at_trigger(pid, table_name) do
    create_updated_at_trigger_statement = """
      CREATE TRIGGER set_#{table_name}_updated_at
      BEFORE UPDATE ON \"#{table_name}\"
      FOR EACH ROW
      EXECUTE PROCEDURE set_current_timestamp_updated_at();
    """

    Postgrex.query!(pid, create_updated_at_trigger_statement, [])
  end

  defp create_user_table(pid) do
    create_user_table_statement = """
      CREATE TABLE "user" (
        id SERIAL PRIMARY KEY,
        email citext UNIQUE NOT NULL,
        name VARCHAR  NOT NULL,
        age INTEGER DEFAULT 0,
        created_at TIMESTAMPTZ DEFAULT NOW(),
        updated_at TIMESTAMPTZ DEFAULT NOW()
      );
    """

    Postgrex.query!(pid, create_user_table_statement, [])
  end

  defp create_todo_table(pid) do
    create_table_statement = """
      CREATE TABLE todo (
          id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
          user_id INTEGER REFERENCES "user"(id) ON DELETE CASCADE,
          description TEXT NOT NULL,
          due_date DATE,
          is_completed BOOLEAN DEFAULT FALSE,
          priority INTEGER DEFAULT 0,
          tags VARCHAR[] DEFAULT '{}'::VARCHAR[],
          rules JSONB,
          created_at TIMESTAMPTZ DEFAULT NOW(),
          updated_at TIMESTAMPTZ DEFAULT NOW()
      );
    """

    Postgrex.query!(pid, create_table_statement, [])
  end

  defp seed_database_tables(pid) do
    seed_users(pid)
    seed_todos(pid)
  end

  defp seed_users(pid) do
    users = """
      INSERT INTO \"user\" (email, name, age)
      VALUES
        ('john.doe@example.com', 'John Doe', 28),
        ('jane.smith@example.com', 'Jane Smith', 32),
        ('bob.jones@example.com', 'Bob Jones', 25),
        ('alice.davis@example.com', 'Alice Davis', 34),
        ('charlie.brown@example.com', 'Charlie Brown', 30);
    """

    Postgrex.query!(pid, users, [])
  end

  defp seed_todos(pid) do
    todos = """
      INSERT INTO todo (user_id, description, due_date, is_completed, priority, tags, rules)
      VALUES
        -- User 1 todos
        (1, 'Buy groceries', '2023-01-10', false, 2, ARRAY['groceries', 'shopping'], '{\"reminder\": true, \"repeat\": \"weekly\"}'::JSONB),
        (1, 'Finish work project', '2023-01-15', true, 1, ARRAY['work', 'project'], '{\"priority\": \"high\"}'::JSONB),
        (1, 'Exercise', NULL, false, 3, ARRAY['health', 'fitness'], '{}'::JSONB),

        -- User 2 todos
        (2, 'Read a book', '2023-02-01', true, 2, ARRAY['reading', 'books'], '{\"genre\": \"mystery\"}'::JSONB),
        (2, 'Write a blog post', '2023-02-10', false, 1, ARRAY['writing', 'blog'], '{\"format\": \"tutorial\"}'::JSONB),
        (2, 'Plan vacation', '2023-03-01', false, 3, ARRAY['travel', 'vacation'], '{\"destination\": \"beach\"}'::JSONB),

        -- User 3 todos
        (3, 'Learn a new programming language', '2023-01-20', false, 2, ARRAY['coding', 'programming'], '{\"level\": \"intermediate\"}'::JSONB),
        (3, 'Cook a new recipe', '2023-02-05', false, 1, ARRAY['cooking', 'recipe'], '{\"cuisine\": \"Italian\"}'::JSONB),
        (3, 'Study for exams', '2023-02-28', true, 3, ARRAY['education', 'exams'], '{\"subject\": \"math\"}'::JSONB),

        -- User 4 todos
        (4, 'Explore hiking trails', NULL, true, 2, ARRAY['outdoors', 'hiking'], '{}'::JSONB),
        (4, 'Complete home improvement projects', '2023-03-15', true, 1, ARRAY['home', 'projects'], '{\"room\": \"kitchen\"}'::JSONB),
        (4, 'Attend a music concert', '2023-04-01', false, 3, ARRAY['music', 'concert'], '{\"genre\": \"rock\"}'::JSONB),

        -- User 5 todos
        (5, 'Volunteer at local community center', '2023-02-10', false, 2, ARRAY['community', 'volunteer'], '{\"activity\": \"food drive\"}'::JSONB),
        (5, 'Practice mindfulness', NULL, false, 1, ARRAY['mindfulness', 'meditation'], '{}'::JSONB),
        (5, 'Attend a language exchange meetup', '2023-03-05', true, 3, ARRAY['language', 'meetup'], '{\"languages\": [\"Spanish\", \"French\"]}'::JSONB)
      ;
    """

    Postgrex.query!(pid, todos, [])
  end
end