From 825ec7deacde2134a1a0ed35eed4face3e143905 Mon Sep 17 00:00:00 2001 From: kjellbergzoey Date: Sun, 7 Jun 2026 02:15:58 +0000 Subject: [PATCH] test: add unit tests for mu-plugin, splash, and nginx-splash Cover three previously-untested pure library modules: - mu-plugin: theme auto-activation, signed-URL auto-login with constant-time secret comparison, injected LiveReload script tag, and configured/default port - splash: HTML document structure and key markers - nginx-splash: catch-all server config and splash try_files Tests only; no source changes. Co-Authored-By: Claude Opus 4.8 --- tests/lib/mu-plugin.test.ts | 93 ++++++++++++++++++++++++++++++++++ tests/lib/nginx-splash.test.ts | 49 ++++++++++++++++++ tests/lib/splash.test.ts | 66 ++++++++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 tests/lib/mu-plugin.test.ts create mode 100644 tests/lib/nginx-splash.test.ts create mode 100644 tests/lib/splash.test.ts diff --git a/tests/lib/mu-plugin.test.ts b/tests/lib/mu-plugin.test.ts new file mode 100644 index 0000000..4c5de8f --- /dev/null +++ b/tests/lib/mu-plugin.test.ts @@ -0,0 +1,93 @@ +import {describe, it, expect, beforeEach, afterEach} from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import {writeMuPlugin} from '../../src/lib/mu-plugin.js'; + +describe('writeMuPlugin', () => { + let tmp: string; + + beforeEach(() => { + tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'kiqr-mu-plugin-')); + }); + + afterEach(() => { + fs.rmSync(tmp, {recursive: true, force: true}); + }); + + it('writes the mu-plugin file and returns its path', () => { + const filePath = writeMuPlugin(tmp); + expect(filePath).toBe(path.join(tmp, 'kiqr-auto-login.php')); + expect(fs.existsSync(filePath)).toBe(true); + }); + + it('creates the runtime directory if it does not exist', () => { + const nested = path.join(tmp, 'a', 'b', 'c'); + const filePath = writeMuPlugin(nested); + expect(fs.existsSync(filePath)).toBe(true); + }); + + it('writes a guarded WordPress PHP plugin header', () => { + const content = fs.readFileSync(writeMuPlugin(tmp), 'utf-8'); + expect(content.startsWith(' { + const content = fs.readFileSync(writeMuPlugin(tmp), 'utf-8'); + expect(content).toContain("add_action('init'"); + expect(content).toContain("getenv('KIQR_THEME_SLUG')"); + expect(content).toContain('switch_theme($slug)'); + // Only activates once, then records that it did so + expect(content).toContain("get_option('kiqr_theme_activated')"); + expect(content).toContain("update_option('kiqr_theme_activated', '1')"); + expect(content).toContain('$theme->exists()'); + }); + + it('implements signed-URL auto-login with constant-time secret comparison', () => { + const content = fs.readFileSync(writeMuPlugin(tmp), 'utf-8'); + expect(content).toContain("$_GET['kiqr_login']"); + expect(content).toContain('is_user_logged_in()'); + expect(content).toContain("getenv('KIQR_LOGIN_SECRET')"); + // hash_equals guards against timing attacks + expect(content).toContain("hash_equals($secret, $_GET['kiqr_login'])"); + expect(content).toContain('wp_set_current_user($user->ID)'); + expect(content).toContain('wp_set_auth_cookie($user->ID, true)'); + }); + + it('falls back to the first administrator when no admin login exists', () => { + const content = fs.readFileSync(writeMuPlugin(tmp), 'utf-8'); + expect(content).toContain("get_user_by('login', 'admin')"); + expect(content).toContain("get_users(['role' => 'administrator', 'number' => 1])"); + }); + + it('redirects and exits after a successful auto-login', () => { + const content = fs.readFileSync(writeMuPlugin(tmp), 'utf-8'); + expect(content).toContain("remove_query_arg('kiqr_login')"); + expect(content).toContain('wp_safe_redirect($redirect ?: admin_url())'); + expect(content).toContain('exit;'); + }); + + it('injects a LiveReload script tag into the footer with the configured port', () => { + const content = fs.readFileSync(writeMuPlugin(tmp), 'utf-8'); + expect(content).toContain("add_action('wp_footer'"); + expect(content).toContain("getenv('KIQR_LIVERELOAD_PORT')"); + expect(content).toContain('