Skip to content
Merged
4 changes: 3 additions & 1 deletion db/knex_migrations/2025-09-02-0000-add-domain-expiry.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ exports.up = function (knex) {
.createTable("domain_expiry", (table) => {
table.increments("id");
table.datetime("last_check");
table.text("domain").unique().notNullable();
// Use VARCHAR(255) for MySQL/MariaDB compatibility with unique constraint
// Maximum domain name length is 253 characters (255 octets on the wire)
table.string("domain", 255).unique().notNullable();
table.datetime("expiry");
table.integer("last_expiry_notification_sent").defaultTo(null);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Ensure domain column is VARCHAR(255) across all database types.
// This migration ensures MySQL, SQLite, and MariaDB have consistent column type,
// even if a user installed 2.1.0-beta.0 or 2.1.0-beta.1 which had TEXT type for this column.
// Maximum domain name length is 253 characters (255 octets on the wire).
// Note: The unique constraint is already present from the original migration.
exports.up = function (knex) {
return knex.schema.alterTable("domain_expiry", function (table) {
table.string("domain", 255).notNullable().alter();
});
};

exports.down = function (knex) {
// No rollback needed - keeping VARCHAR(255) is the correct state
};
58 changes: 58 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
"@testcontainers/hivemq": "^10.13.1",
"@testcontainers/mariadb": "^10.13.0",
"@testcontainers/mssqlserver": "^10.28.0",
"@testcontainers/mysql": "^11.11.0",
"@testcontainers/postgresql": "^11.9.0",
"@testcontainers/rabbitmq": "^10.13.2",
"@types/bootstrap": "~5.1.9",
Expand Down
65 changes: 65 additions & 0 deletions test/backend-test/test-migration.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const { describe, test } = require("node:test");
const fs = require("fs");
const path = require("path");
const { GenericContainer, Wait } = require("testcontainers");
const { MySqlContainer } = require("@testcontainers/mysql");

describe("Database Migration", () => {
test("SQLite migrations run successfully from fresh database", async () => {
Expand Down Expand Up @@ -130,4 +131,68 @@ describe("Database Migration", () => {
}
}
);

test(
"MySQL migrations run successfully from fresh database",
{
skip:
!!process.env.CI &&
(process.platform !== "linux" || process.arch !== "x64"),
},
async () => {
// Start MySQL 8.0 container (the version mentioned in the issue)
const mysqlContainer = await new MySqlContainer("mysql:8.0")
.withStartupTimeout(120000)
.start();

const knex = require("knex");
const knexInstance = knex({
client: "mysql2",
connection: {
host: mysqlContainer.getHost(),
port: mysqlContainer.getPort(),
user: mysqlContainer.getUsername(),
password: mysqlContainer.getUserPassword(),
database: mysqlContainer.getDatabase(),
connectTimeout: 60000,
},
pool: {
min: 0,
max: 10,
acquireTimeoutMillis: 60000,
idleTimeoutMillis: 60000,
},
});

// Setup R (redbean) with knex instance like production code does
const { R } = require("redbean-node");
R.setup(knexInstance);

try {
// Use production code to initialize MySQL tables
const { createTables } = require("../../db/knex_init_db.js");
await createTables();

// Run all migrations like production code does
await R.knex.migrate.latest({
directory: path.join(__dirname, "../../db/knex_migrations")
});

// Test passes if migrations complete successfully without errors

} finally {
// Clean up
try {
await R.knex.destroy();
} catch (e) {
// Ignore cleanup errors
}
try {
await mysqlContainer.stop();
} catch (e) {
// Ignore cleanup errors
}
}
}
);
});
Loading