From 450dba1d7421e0e7e90d9f2e18c29b19f4e399ab Mon Sep 17 00:00:00 2001
From: punkfairie <marley@punkfairie.net>
Date: Sun, 23 Feb 2025 20:44:23 -0800
Subject: [PATCH] testing

---
 docker-compose.yml                        |  4 +-
 docker/8.0/Dockerfile                     | 70 ++++++++++++++++++++++
 docker/8.0/php.ini                        |  5 ++
 docker/8.0/start-container                | 26 +++++++++
 docker/8.0/supervisord.conf               | 14 +++++
 docker/8.1/Dockerfile                     | 69 ++++++++++++++++++++++
 docker/8.1/php.ini                        |  5 ++
 docker/8.1/start-container                | 26 +++++++++
 docker/8.1/supervisord.conf               | 14 +++++
 docker/8.2/Dockerfile                     | 70 ++++++++++++++++++++++
 docker/8.2/php.ini                        |  5 ++
 docker/8.2/start-container                | 26 +++++++++
 docker/8.2/supervisord.conf               | 14 +++++
 docker/8.3/Dockerfile                     | 71 +++++++++++++++++++++++
 docker/8.3/php.ini                        |  5 ++
 docker/8.3/start-container                | 26 +++++++++
 docker/8.3/supervisord.conf               | 14 +++++
 docker/8.4/Dockerfile                     | 71 +++++++++++++++++++++++
 docker/8.4/php.ini                        |  8 +++
 docker/8.4/start-container                | 26 +++++++++
 docker/8.4/supervisord.conf               | 14 +++++
 docker/mariadb/create-testing-database.sh |  6 ++
 docker/mysql/create-testing-database.sh   |  6 ++
 docker/pgsql/create-testing-database.sql  |  2 +
 tests/Feature/CategoryTest.php            | 40 +++++++++++++
 tests/Pest.php                            | 24 ++++++--
 tests/TestCase.php                        |  8 ++-
 27 files changed, 662 insertions(+), 7 deletions(-)
 create mode 100644 docker/8.0/Dockerfile
 create mode 100644 docker/8.0/php.ini
 create mode 100644 docker/8.0/start-container
 create mode 100644 docker/8.0/supervisord.conf
 create mode 100644 docker/8.1/Dockerfile
 create mode 100644 docker/8.1/php.ini
 create mode 100644 docker/8.1/start-container
 create mode 100644 docker/8.1/supervisord.conf
 create mode 100644 docker/8.2/Dockerfile
 create mode 100644 docker/8.2/php.ini
 create mode 100644 docker/8.2/start-container
 create mode 100644 docker/8.2/supervisord.conf
 create mode 100644 docker/8.3/Dockerfile
 create mode 100644 docker/8.3/php.ini
 create mode 100644 docker/8.3/start-container
 create mode 100644 docker/8.3/supervisord.conf
 create mode 100644 docker/8.4/Dockerfile
 create mode 100644 docker/8.4/php.ini
 create mode 100644 docker/8.4/start-container
 create mode 100644 docker/8.4/supervisord.conf
 create mode 100644 docker/mariadb/create-testing-database.sh
 create mode 100644 docker/mysql/create-testing-database.sh
 create mode 100644 docker/pgsql/create-testing-database.sql
 create mode 100644 tests/Feature/CategoryTest.php

diff --git a/docker-compose.yml b/docker-compose.yml
index 42dd520..5a66b5a 100755
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,7 +1,7 @@
 services:
     laravel.test:
         build:
-            context: './vendor/laravel/sail/runtimes/8.4'
+            context: './docker/8.4'
             dockerfile: Dockerfile
             args:
                 WWWGROUP: '${WWWGROUP}'
@@ -38,7 +38,7 @@ services:
             POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}'
         volumes:
             - 'sail-pgsql:/var/lib/postgresql/data'
-            - './vendor/laravel/sail/database/pgsql/create-testing-database.sql:/docker-entrypoint-initdb.d/10-create-testing-database.sql'
+            - './docker/pgsql/create-testing-database.sql:/docker-entrypoint-initdb.d/10-create-testing-database.sql'
         networks:
             - sail
         healthcheck:
diff --git a/docker/8.0/Dockerfile b/docker/8.0/Dockerfile
new file mode 100644
index 0000000..b7b34e2
--- /dev/null
+++ b/docker/8.0/Dockerfile
@@ -0,0 +1,70 @@
+FROM ubuntu:24.04
+
+LABEL maintainer="Taylor Otwell"
+
+ARG WWWGROUP
+ARG NODE_VERSION=22
+ARG POSTGRES_VERSION=17
+
+WORKDIR /var/www/html
+
+ENV DEBIAN_FRONTEND=noninteractive
+ENV TZ=UTC
+ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80"
+ENV SUPERVISOR_PHP_USER="sail"
+
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \
+    echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \
+    echo "Acquire::BrokenProxy    true;" >> /etc/apt/apt.conf.d/99custom
+
+RUN apt-get update && apt-get upgrade -y \
+    && mkdir -p /etc/apt/keyrings \
+    && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \
+    && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /usr/share/keyrings/ppa_ondrej_php.gpg > /dev/null \
+    && echo "deb [signed-by=/usr/share/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
+    && apt-get update \
+    && apt-get install -y php8.0-cli php8.0-dev \
+       php8.0-pgsql php8.0-sqlite3 php8.0-gd php8.0-imagick \
+       php8.0-curl php8.0-memcached php8.0-mongodb \
+       php8.0-imap php8.0-mysql php8.0-mbstring \
+       php8.0-xml php8.0-zip php8.0-bcmath php8.0-soap \
+       php8.0-intl php8.0-readline php8.0-pcov \
+       php8.0-msgpack php8.0-igbinary php8.0-ldap \
+       php8.0-redis php8.0-swoole php8.0-xdebug \
+    && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \
+    && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
+    && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
+    && apt-get update \
+    && apt-get install -y nodejs \
+    && npm install -g npm \
+    && npm install -g bun \
+    && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /usr/share/keyrings/yarnkey.gpg >/dev/null \
+    && echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
+    && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/pgdg.gpg >/dev/null \
+    && echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
+    && apt-get update \
+    && apt-get install -y yarn \
+    && apt-get install -y mysql-client \
+    && apt-get install -y postgresql-client-$POSTGRES_VERSION \
+    && apt-get -y autoremove \
+    && apt-get clean \
+    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
+
+RUN update-alternatives --set php /usr/bin/php8.0
+
+RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.0
+
+RUN userdel -r ubuntu
+RUN groupadd --force -g $WWWGROUP sail
+RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail
+
+COPY start-container /usr/local/bin/start-container
+COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
+COPY php.ini /etc/php/8.0/cli/conf.d/99-sail.ini
+RUN chmod +x /usr/local/bin/start-container
+
+EXPOSE 80/tcp
+
+ENTRYPOINT ["start-container"]
diff --git a/docker/8.0/php.ini b/docker/8.0/php.ini
new file mode 100644
index 0000000..0d8ce9e
--- /dev/null
+++ b/docker/8.0/php.ini
@@ -0,0 +1,5 @@
+[PHP]
+post_max_size = 100M
+upload_max_filesize = 100M
+variables_order = EGPCS
+pcov.directory = .
diff --git a/docker/8.0/start-container b/docker/8.0/start-container
new file mode 100644
index 0000000..40c55df
--- /dev/null
+++ b/docker/8.0/start-container
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then
+    echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'."
+    exit 1
+fi
+
+if [ ! -z "$WWWUSER" ]; then
+    usermod -u $WWWUSER sail
+fi
+
+if [ ! -d /.composer ]; then
+    mkdir /.composer
+fi
+
+chmod -R ugo+rw /.composer
+
+if [ $# -gt 0 ]; then
+    if [ "$SUPERVISOR_PHP_USER" = "root" ]; then
+        exec "$@"
+    else
+        exec gosu $WWWUSER "$@"
+    fi
+else
+    exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
+fi
diff --git a/docker/8.0/supervisord.conf b/docker/8.0/supervisord.conf
new file mode 100644
index 0000000..656da8a
--- /dev/null
+++ b/docker/8.0/supervisord.conf
@@ -0,0 +1,14 @@
+[supervisord]
+nodaemon=true
+user=root
+logfile=/var/log/supervisor/supervisord.log
+pidfile=/var/run/supervisord.pid
+
+[program:php]
+command=%(ENV_SUPERVISOR_PHP_COMMAND)s
+user=%(ENV_SUPERVISOR_PHP_USER)s
+environment=LARAVEL_SAIL="1"
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0
+stderr_logfile=/dev/stderr
+stderr_logfile_maxbytes=0
diff --git a/docker/8.1/Dockerfile b/docker/8.1/Dockerfile
new file mode 100644
index 0000000..cc5c611
--- /dev/null
+++ b/docker/8.1/Dockerfile
@@ -0,0 +1,69 @@
+FROM ubuntu:24.04
+
+LABEL maintainer="Taylor Otwell"
+
+ARG WWWGROUP
+ARG NODE_VERSION=22
+ARG POSTGRES_VERSION=17
+
+WORKDIR /var/www/html
+
+ENV DEBIAN_FRONTEND=noninteractive
+ENV TZ=UTC
+ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80"
+ENV SUPERVISOR_PHP_USER="sail"
+
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \
+    echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \
+    echo "Acquire::BrokenProxy    true;" >> /etc/apt/apt.conf.d/99custom
+
+RUN apt-get update && apt-get upgrade -y \
+    && mkdir -p /etc/apt/keyrings \
+    && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \
+    && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /usr/share/keyrings/ppa_ondrej_php.gpg > /dev/null \
+    && echo "deb [signed-by=/usr/share/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
+    && apt-get update \
+    && apt-get install -y php8.1-cli php8.1-dev \
+       php8.1-pgsql php8.1-sqlite3 php8.1-gd php8.1-imagick \
+       php8.1-curl php8.1-mongodb \
+       php8.1-imap php8.1-mysql php8.1-mbstring \
+       php8.1-xml php8.1-zip php8.1-bcmath php8.1-soap \
+       php8.1-intl php8.1-readline \
+       php8.1-ldap \
+       php8.1-msgpack php8.1-igbinary php8.1-redis php8.1-swoole \
+       php8.1-memcached php8.1-pcov php8.1-xdebug \
+    && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \
+    && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
+    && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
+    && apt-get update \
+    && apt-get install -y nodejs \
+    && npm install -g npm \
+    && npm install -g bun \
+    && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /usr/share/keyrings/yarn.gpg >/dev/null \
+    && echo "deb [signed-by=/usr/share/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
+    && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/pgdg.gpg >/dev/null \
+    && echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
+    && apt-get update \
+    && apt-get install -y yarn \
+    && apt-get install -y mysql-client \
+    && apt-get install -y postgresql-client-$POSTGRES_VERSION \
+    && apt-get -y autoremove \
+    && apt-get clean \
+    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
+
+RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.1
+
+RUN userdel -r ubuntu
+RUN groupadd --force -g $WWWGROUP sail
+RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail
+
+COPY start-container /usr/local/bin/start-container
+COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
+COPY php.ini /etc/php/8.1/cli/conf.d/99-sail.ini
+RUN chmod +x /usr/local/bin/start-container
+
+EXPOSE 80/tcp
+
+ENTRYPOINT ["start-container"]
diff --git a/docker/8.1/php.ini b/docker/8.1/php.ini
new file mode 100644
index 0000000..0d8ce9e
--- /dev/null
+++ b/docker/8.1/php.ini
@@ -0,0 +1,5 @@
+[PHP]
+post_max_size = 100M
+upload_max_filesize = 100M
+variables_order = EGPCS
+pcov.directory = .
diff --git a/docker/8.1/start-container b/docker/8.1/start-container
new file mode 100644
index 0000000..40c55df
--- /dev/null
+++ b/docker/8.1/start-container
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then
+    echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'."
+    exit 1
+fi
+
+if [ ! -z "$WWWUSER" ]; then
+    usermod -u $WWWUSER sail
+fi
+
+if [ ! -d /.composer ]; then
+    mkdir /.composer
+fi
+
+chmod -R ugo+rw /.composer
+
+if [ $# -gt 0 ]; then
+    if [ "$SUPERVISOR_PHP_USER" = "root" ]; then
+        exec "$@"
+    else
+        exec gosu $WWWUSER "$@"
+    fi
+else
+    exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
+fi
diff --git a/docker/8.1/supervisord.conf b/docker/8.1/supervisord.conf
new file mode 100644
index 0000000..656da8a
--- /dev/null
+++ b/docker/8.1/supervisord.conf
@@ -0,0 +1,14 @@
+[supervisord]
+nodaemon=true
+user=root
+logfile=/var/log/supervisor/supervisord.log
+pidfile=/var/run/supervisord.pid
+
+[program:php]
+command=%(ENV_SUPERVISOR_PHP_COMMAND)s
+user=%(ENV_SUPERVISOR_PHP_USER)s
+environment=LARAVEL_SAIL="1"
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0
+stderr_logfile=/dev/stderr
+stderr_logfile_maxbytes=0
diff --git a/docker/8.2/Dockerfile b/docker/8.2/Dockerfile
new file mode 100644
index 0000000..536dffe
--- /dev/null
+++ b/docker/8.2/Dockerfile
@@ -0,0 +1,70 @@
+FROM ubuntu:24.04
+
+LABEL maintainer="Taylor Otwell"
+
+ARG WWWGROUP
+ARG NODE_VERSION=22
+ARG POSTGRES_VERSION=17
+
+WORKDIR /var/www/html
+
+ENV DEBIAN_FRONTEND=noninteractive
+ENV TZ=UTC
+ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80"
+ENV SUPERVISOR_PHP_USER="sail"
+
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \
+    echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \
+    echo "Acquire::BrokenProxy    true;" >> /etc/apt/apt.conf.d/99custom
+
+RUN apt-get update && apt-get upgrade -y \
+    && mkdir -p /etc/apt/keyrings \
+    && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano  \
+    && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \
+    && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
+    && apt-get update \
+    && apt-get install -y php8.2-cli php8.2-dev \
+       php8.2-pgsql php8.2-sqlite3 php8.2-gd php8.2-imagick \
+       php8.2-curl php8.2-mongodb \
+       php8.2-imap php8.2-mysql php8.2-mbstring \
+       php8.2-xml php8.2-zip php8.2-bcmath php8.2-soap \
+       php8.2-intl php8.2-readline \
+       php8.2-ldap \
+       php8.2-msgpack php8.2-igbinary php8.2-redis php8.2-swoole \
+       php8.2-memcached php8.2-pcov php8.2-xdebug \
+    && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \
+    && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
+    && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
+    && apt-get update \
+    && apt-get install -y nodejs \
+    && npm install -g npm \
+    && npm install -g pnpm \
+    && npm install -g bun \
+    && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \
+    && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
+    && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \
+    && echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
+    && apt-get update \
+    && apt-get install -y yarn \
+    && apt-get install -y mysql-client \
+    && apt-get install -y postgresql-client-$POSTGRES_VERSION \
+    && apt-get -y autoremove \
+    && apt-get clean \
+    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
+
+RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.2
+
+RUN userdel -r ubuntu
+RUN groupadd --force -g $WWWGROUP sail
+RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail
+
+COPY start-container /usr/local/bin/start-container
+COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
+COPY php.ini /etc/php/8.2/cli/conf.d/99-sail.ini
+RUN chmod +x /usr/local/bin/start-container
+
+EXPOSE 80/tcp
+
+ENTRYPOINT ["start-container"]
diff --git a/docker/8.2/php.ini b/docker/8.2/php.ini
new file mode 100644
index 0000000..0d8ce9e
--- /dev/null
+++ b/docker/8.2/php.ini
@@ -0,0 +1,5 @@
+[PHP]
+post_max_size = 100M
+upload_max_filesize = 100M
+variables_order = EGPCS
+pcov.directory = .
diff --git a/docker/8.2/start-container b/docker/8.2/start-container
new file mode 100644
index 0000000..40c55df
--- /dev/null
+++ b/docker/8.2/start-container
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then
+    echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'."
+    exit 1
+fi
+
+if [ ! -z "$WWWUSER" ]; then
+    usermod -u $WWWUSER sail
+fi
+
+if [ ! -d /.composer ]; then
+    mkdir /.composer
+fi
+
+chmod -R ugo+rw /.composer
+
+if [ $# -gt 0 ]; then
+    if [ "$SUPERVISOR_PHP_USER" = "root" ]; then
+        exec "$@"
+    else
+        exec gosu $WWWUSER "$@"
+    fi
+else
+    exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
+fi
diff --git a/docker/8.2/supervisord.conf b/docker/8.2/supervisord.conf
new file mode 100644
index 0000000..656da8a
--- /dev/null
+++ b/docker/8.2/supervisord.conf
@@ -0,0 +1,14 @@
+[supervisord]
+nodaemon=true
+user=root
+logfile=/var/log/supervisor/supervisord.log
+pidfile=/var/run/supervisord.pid
+
+[program:php]
+command=%(ENV_SUPERVISOR_PHP_COMMAND)s
+user=%(ENV_SUPERVISOR_PHP_USER)s
+environment=LARAVEL_SAIL="1"
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0
+stderr_logfile=/dev/stderr
+stderr_logfile_maxbytes=0
diff --git a/docker/8.3/Dockerfile b/docker/8.3/Dockerfile
new file mode 100644
index 0000000..ef5904b
--- /dev/null
+++ b/docker/8.3/Dockerfile
@@ -0,0 +1,71 @@
+FROM ubuntu:24.04
+
+LABEL maintainer="Taylor Otwell"
+
+ARG WWWGROUP
+ARG NODE_VERSION=22
+ARG MYSQL_CLIENT="mysql-client"
+ARG POSTGRES_VERSION=17
+
+WORKDIR /var/www/html
+
+ENV DEBIAN_FRONTEND=noninteractive
+ENV TZ=UTC
+ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80"
+ENV SUPERVISOR_PHP_USER="sail"
+
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \
+    echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \
+    echo "Acquire::BrokenProxy    true;" >> /etc/apt/apt.conf.d/99custom
+
+RUN apt-get update && apt-get upgrade -y \
+    && mkdir -p /etc/apt/keyrings \
+    && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano  \
+    && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \
+    && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
+    && apt-get update \
+    && apt-get install -y php8.3-cli php8.3-dev \
+       php8.3-pgsql php8.3-sqlite3 php8.3-gd \
+       php8.3-curl php8.3-mongodb \
+       php8.3-imap php8.3-mysql php8.3-mbstring \
+       php8.3-xml php8.3-zip php8.3-bcmath php8.3-soap \
+       php8.3-intl php8.3-readline \
+       php8.3-ldap \
+       php8.3-msgpack php8.3-igbinary php8.3-redis \
+       php8.3-memcached php8.3-pcov php8.3-imagick php8.3-xdebug php8.3-swoole \
+    && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \
+    && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
+    && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
+    && apt-get update \
+    && apt-get install -y nodejs \
+    && npm install -g npm \
+    && npm install -g pnpm \
+    && npm install -g bun \
+    && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \
+    && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
+    && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \
+    && echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
+    && apt-get update \
+    && apt-get install -y yarn \
+    && apt-get install -y $MYSQL_CLIENT \
+    && apt-get install -y postgresql-client-$POSTGRES_VERSION \
+    && apt-get -y autoremove \
+    && apt-get clean \
+    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
+
+RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.3
+
+RUN userdel -r ubuntu
+RUN groupadd --force -g $WWWGROUP sail
+RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail
+
+COPY start-container /usr/local/bin/start-container
+COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
+COPY php.ini /etc/php/8.3/cli/conf.d/99-sail.ini
+RUN chmod +x /usr/local/bin/start-container
+
+EXPOSE 80/tcp
+
+ENTRYPOINT ["start-container"]
diff --git a/docker/8.3/php.ini b/docker/8.3/php.ini
new file mode 100644
index 0000000..0d8ce9e
--- /dev/null
+++ b/docker/8.3/php.ini
@@ -0,0 +1,5 @@
+[PHP]
+post_max_size = 100M
+upload_max_filesize = 100M
+variables_order = EGPCS
+pcov.directory = .
diff --git a/docker/8.3/start-container b/docker/8.3/start-container
new file mode 100644
index 0000000..40c55df
--- /dev/null
+++ b/docker/8.3/start-container
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then
+    echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'."
+    exit 1
+fi
+
+if [ ! -z "$WWWUSER" ]; then
+    usermod -u $WWWUSER sail
+fi
+
+if [ ! -d /.composer ]; then
+    mkdir /.composer
+fi
+
+chmod -R ugo+rw /.composer
+
+if [ $# -gt 0 ]; then
+    if [ "$SUPERVISOR_PHP_USER" = "root" ]; then
+        exec "$@"
+    else
+        exec gosu $WWWUSER "$@"
+    fi
+else
+    exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
+fi
diff --git a/docker/8.3/supervisord.conf b/docker/8.3/supervisord.conf
new file mode 100644
index 0000000..656da8a
--- /dev/null
+++ b/docker/8.3/supervisord.conf
@@ -0,0 +1,14 @@
+[supervisord]
+nodaemon=true
+user=root
+logfile=/var/log/supervisor/supervisord.log
+pidfile=/var/run/supervisord.pid
+
+[program:php]
+command=%(ENV_SUPERVISOR_PHP_COMMAND)s
+user=%(ENV_SUPERVISOR_PHP_USER)s
+environment=LARAVEL_SAIL="1"
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0
+stderr_logfile=/dev/stderr
+stderr_logfile_maxbytes=0
diff --git a/docker/8.4/Dockerfile b/docker/8.4/Dockerfile
new file mode 100644
index 0000000..cb0fbdc
--- /dev/null
+++ b/docker/8.4/Dockerfile
@@ -0,0 +1,71 @@
+FROM ubuntu:24.04
+
+LABEL maintainer="Taylor Otwell"
+
+ARG WWWGROUP
+ARG NODE_VERSION=22
+ARG MYSQL_CLIENT="mysql-client"
+ARG POSTGRES_VERSION=17
+
+WORKDIR /var/www/html
+
+ENV DEBIAN_FRONTEND=noninteractive
+ENV TZ=UTC
+ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80"
+ENV SUPERVISOR_PHP_USER="sail"
+
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \
+    echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \
+    echo "Acquire::BrokenProxy    true;" >> /etc/apt/apt.conf.d/99custom
+
+RUN apt-get update && apt-get upgrade -y \
+    && mkdir -p /etc/apt/keyrings \
+    && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano  \
+    && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \
+    && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
+    && apt-get update \
+    && apt-get install -y php8.4-cli php8.4-dev \
+       php8.4-pgsql php8.4-sqlite3 php8.4-gd \
+       php8.4-curl php8.4-mongodb \
+       php8.4-imap php8.4-mysql php8.4-mbstring \
+       php8.4-xml php8.4-zip php8.4-bcmath php8.4-soap \
+       php8.4-intl php8.4-readline \
+       php8.4-ldap \
+       php8.4-msgpack php8.4-igbinary php8.4-redis php8.4-swoole \
+       php8.4-memcached php8.4-pcov php8.4-imagick php8.4-xdebug \
+    && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \
+    && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
+    && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
+    && apt-get update \
+    && apt-get install -y nodejs \
+    && npm install -g npm \
+    && npm install -g pnpm \
+    && npm install -g bun \
+    && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \
+    && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
+    && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \
+    && echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
+    && apt-get update \
+    && apt-get install -y yarn \
+    && apt-get install -y $MYSQL_CLIENT \
+    && apt-get install -y postgresql-client-$POSTGRES_VERSION \
+    && apt-get -y autoremove \
+    && apt-get clean \
+    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
+
+RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.4
+
+RUN userdel -r ubuntu
+RUN groupadd --force -g $WWWGROUP sail
+RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail
+
+COPY start-container /usr/local/bin/start-container
+COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
+COPY php.ini /etc/php/8.4/cli/conf.d/99-sail.ini
+RUN chmod +x /usr/local/bin/start-container
+
+EXPOSE 80/tcp
+
+ENTRYPOINT ["start-container"]
diff --git a/docker/8.4/php.ini b/docker/8.4/php.ini
new file mode 100644
index 0000000..0cf80e2
--- /dev/null
+++ b/docker/8.4/php.ini
@@ -0,0 +1,8 @@
+[PHP]
+post_max_size = 100M
+upload_max_filesize = 100M
+variables_order = EGPCS
+pcov.directory = .
+
+[xdebug]
+xdebug.mode = ${XDEBUG_MODE}
diff --git a/docker/8.4/start-container b/docker/8.4/start-container
new file mode 100644
index 0000000..40c55df
--- /dev/null
+++ b/docker/8.4/start-container
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then
+    echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'."
+    exit 1
+fi
+
+if [ ! -z "$WWWUSER" ]; then
+    usermod -u $WWWUSER sail
+fi
+
+if [ ! -d /.composer ]; then
+    mkdir /.composer
+fi
+
+chmod -R ugo+rw /.composer
+
+if [ $# -gt 0 ]; then
+    if [ "$SUPERVISOR_PHP_USER" = "root" ]; then
+        exec "$@"
+    else
+        exec gosu $WWWUSER "$@"
+    fi
+else
+    exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
+fi
diff --git a/docker/8.4/supervisord.conf b/docker/8.4/supervisord.conf
new file mode 100644
index 0000000..656da8a
--- /dev/null
+++ b/docker/8.4/supervisord.conf
@@ -0,0 +1,14 @@
+[supervisord]
+nodaemon=true
+user=root
+logfile=/var/log/supervisor/supervisord.log
+pidfile=/var/run/supervisord.pid
+
+[program:php]
+command=%(ENV_SUPERVISOR_PHP_COMMAND)s
+user=%(ENV_SUPERVISOR_PHP_USER)s
+environment=LARAVEL_SAIL="1"
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0
+stderr_logfile=/dev/stderr
+stderr_logfile_maxbytes=0
diff --git a/docker/mariadb/create-testing-database.sh b/docker/mariadb/create-testing-database.sh
new file mode 100644
index 0000000..d3b19d9
--- /dev/null
+++ b/docker/mariadb/create-testing-database.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+/usr/bin/mariadb --user=root --password="$MYSQL_ROOT_PASSWORD" <<-EOSQL
+    CREATE DATABASE IF NOT EXISTS testing;
+    GRANT ALL PRIVILEGES ON \`testing%\`.* TO '$MYSQL_USER'@'%';
+EOSQL
diff --git a/docker/mysql/create-testing-database.sh b/docker/mysql/create-testing-database.sh
new file mode 100644
index 0000000..aeb1826
--- /dev/null
+++ b/docker/mysql/create-testing-database.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+mysql --user=root --password="$MYSQL_ROOT_PASSWORD" <<-EOSQL
+    CREATE DATABASE IF NOT EXISTS testing;
+    GRANT ALL PRIVILEGES ON \`testing%\`.* TO '$MYSQL_USER'@'%';
+EOSQL
diff --git a/docker/pgsql/create-testing-database.sql b/docker/pgsql/create-testing-database.sql
new file mode 100644
index 0000000..d84dc07
--- /dev/null
+++ b/docker/pgsql/create-testing-database.sql
@@ -0,0 +1,2 @@
+SELECT 'CREATE DATABASE testing'
+WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'testing')\gexec
diff --git a/tests/Feature/CategoryTest.php b/tests/Feature/CategoryTest.php
new file mode 100644
index 0000000..b9bf250
--- /dev/null
+++ b/tests/Feature/CategoryTest.php
@@ -0,0 +1,40 @@
+<?php
+
+use App\Http\Controllers\CategoryController;
+use App\Models\Category;
+use App\Models\User;
+
+covers(CategoryController::class);
+
+test('guests can view category index', function () {
+    $response = $this->get(route('categories.index'));
+
+    $response->assertOk();
+});
+
+test('category index request contains categories', function () {
+    $response = $this->get(route('categories.index'));
+
+    $response->assertViewHas('categories');
+    expect($response['categories'])
+        ->toBeEloquentCollection()
+        ->toContainOnlyInstancesOf(Category::class)
+        ->toEqual(Category::all());
+});
+
+test('category index request contains trashedCategories', function () {
+    $response = $this->get(route('categories.index'));
+
+    $response->assertViewHas('trashedCategories');
+    expect($response['trashedCategories'])
+        ->toBeEloquentCollection()
+        ->toContainOnlyInstancesOf(Category::class)
+        ->toEqual(Category::onlyTrashed()->get());
+});
+
+test('logged in users can view category index', function () {
+    $user = User::factory()->create();
+    $response = $this->actingAs($user)->get(route('categories.index'));
+
+    $response->assertOk();
+});
diff --git a/tests/Pest.php b/tests/Pest.php
index 40d096b..2e87deb 100755
--- a/tests/Pest.php
+++ b/tests/Pest.php
@@ -11,10 +11,16 @@
 |
 */
 
-pest()->extend(Tests\TestCase::class)
+use Illuminate\Database\Eloquent\Collection;
+use Pest\Expectation;
+
+pest()
+    ->extend(Tests\TestCase::class)
     ->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
     ->in('Feature');
 
+arch()->preset()->laravel();
+
 /*
 |--------------------------------------------------------------------------
 | Expectations
@@ -26,9 +32,19 @@
 |
 */
 
-expect()->extend('toBeOne', function () {
-    return $this->toBe(1);
-});
+//expect()->extend(
+//    'collectionToContainModel',
+//    function (Model): Expectation {
+//        return $this->
+//    }
+//);
+
+expect()->extend(
+    'toBeEloquentCollection',
+    function (): Expectation {
+        return $this->toBeInstanceOf(Collection::class);
+    }
+);
 
 /*
 |--------------------------------------------------------------------------
diff --git a/tests/TestCase.php b/tests/TestCase.php
index fe1ffc2..a0f65fe 100755
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -6,5 +6,11 @@
 
 abstract class TestCase extends BaseTestCase
 {
-    //
+    /**
+     * Indicates whether the default seeder should run before each test.
+     *
+     * @var bool
+     * @noinspection PhpMissingFieldTypeInspection
+     */
+    protected $seed = true;
 }