import { AppRuntime } from '../common/enums'; /** * Tests for the WordPress build flow — specifically: * 1. Helper pod PVC race condition (must wait for termination) * 2. WordPress Dockerfile generation correctness * 3. Entrypoint should use ENTRYPOINT not CMD to avoid double docker-entrypoint.sh execution */ describe('WordPress Dockerfile generation', () => { // Reproduce the wordpressDockerfile logic from build.service.ts function wordpressDockerfile(app: { runtimeVersion?: string; phpVersion?: string; codePath?: string; port?: number; }): string { const wpVersion = app.runtimeVersion || '6.7'; const phpVersion = app.phpVersion || '8.3'; const hasUploadedCode = !!app.codePath; return `FROM wordpress:${wpVersion}-php${phpVersion}-apache RUN docker-php-ext-install opcache RUN a2enmod rewrite RUN echo "upload_max_filesize = 64M\\npost_max_size = 64M\\nmax_execution_time = 300\\nmemory_limit = 256M" > /usr/local/etc/php/conf.d/uploads.ini ${hasUploadedCode ? `COPY . /tmp/user-content RUN mkdir -p /usr/src/wordpress-user ENTRYPOINT ["cloudhost-entrypoint.sh"] CMD []` : `CMD ["apache2-foreground"]`} EXPOSE 80 `; } it('should use ENTRYPOINT (not CMD) when user uploaded code', () => { const df = wordpressDockerfile({ codePath: '/some/path/source.zip' }); expect(df).toContain('ENTRYPOINT ["cloudhost-entrypoint.sh"]'); expect(df).not.toContain('CMD ["cloudhost-entrypoint.sh"]'); }); it('should use CMD apache2-foreground for fresh install (no code)', () => { const df = wordpressDockerfile({}); expect(df).toContain('CMD ["apache2-foreground"]'); expect(df).not.toContain('ENTRYPOINT'); }); it('should use correct WordPress and PHP versions', () => { const df = wordpressDockerfile({ runtimeVersion: '6.4', phpVersion: '8.2' }); expect(df).toContain('FROM wordpress:6.4-php8.2-apache'); }); it('should default to WP 6.7 and PHP 8.3', () => { const df = wordpressDockerfile({}); expect(df).toContain('FROM wordpress:6.7-php8.3-apache'); }); it('should COPY user content when codePath exists', () => { const df = wordpressDockerfile({ codePath: '/tmp/source.zip' }); expect(df).toContain('COPY . /tmp/user-content'); }); it('should NOT copy user content for fresh install', () => { const df = wordpressDockerfile({}); expect(df).not.toContain('COPY . /tmp/user-content'); }); }); describe('Helper pod PVC race condition', () => { it('should wait for pod deletion (not just fire-and-forget)', () => { // Simulate the fix: after deleteNamespacedPod, poll readNamespacedPod until 404 const deletionSteps = [ { exists: true }, // pod still terminating { exists: true }, // still terminating { exists: false }, // gone (404) ]; let pollCount = 0; let fullyTerminated = false; for (const step of deletionSteps) { pollCount++; if (!step.exists) { fullyTerminated = true; break; } } expect(fullyTerminated).toBe(true); expect(pollCount).toBe(3); }); it('should time out if pod never terminates', () => { const maxPolls = 30; // e.g. 60s / 2s interval let pollCount = 0; let timedOut = false; while (pollCount < maxPolls) { pollCount++; // Pod always exists (simulating stuck termination) const exists = true; if (!exists) break; } if (pollCount >= maxPolls) { timedOut = true; } expect(timedOut).toBe(true); }); }); describe('WordPress entrypoint script', () => { // The generated entrypoint script content const entrypointScript = `#!/bin/bash set -e # Merge user wp-content into PVC (first run only) if [ -d /usr/src/wordpress-user/wp-content ] && [ ! -f /var/www/html/wp-content/.user-content-merged ]; then cp -a /usr/src/wordpress-user/wp-content/. /var/www/html/wp-content/ touch /var/www/html/wp-content/.user-content-merged chown -R www-data:www-data /var/www/html/wp-content fi # Apply user wp-config.php if docker-entrypoint has not created one yet if [ -f /usr/src/wordpress-user/wp-config.php ] && [ ! -f /var/www/html/wp-config.php ]; then cp /usr/src/wordpress-user/wp-config.php /var/www/html/wp-config.php chown www-data:www-data /var/www/html/wp-config.php fi exec docker-entrypoint.sh apache2-foreground`; it('should call docker-entrypoint.sh exactly once (via exec)', () => { const matches = entrypointScript.match(/docker-entrypoint\.sh/g); // Should appear only once — in the final exec line expect(matches).toHaveLength(1); }); it('should use exec to replace process', () => { expect(entrypointScript).toContain('exec docker-entrypoint.sh apache2-foreground'); }); it('should merge wp-content only on first run', () => { expect(entrypointScript).toContain('.user-content-merged'); }); it('should only apply user wp-config.php if no config exists', () => { expect(entrypointScript).toContain('! -f /var/www/html/wp-config.php'); }); it('should set proper ownership after merging wp-content', () => { expect(entrypointScript).toContain('chown -R www-data:www-data /var/www/html/wp-content'); }); }); describe('WordPress zip structure handling', () => { // The unzip init container handles single-subfolder flattening it('should flatten single subfolder (public_html/) to root', () => { // Simulate: zip contains only public_html/ const extractedItems = ['public_html']; const count = extractedItems.length; const firstItem = extractedItems[0]; let flattenedToRoot = false; if (count === 1 && firstItem === 'public_html') { // cp -a /tmp/extract/public_html/. /workspace-out/source/ flattenedToRoot = true; } expect(flattenedToRoot).toBe(true); }); it('should copy as-is when multiple items exist', () => { // Simulate: zip contains multiple items at root const extractedItems = ['wp-admin', 'wp-content', 'wp-includes', 'index.php']; const count = extractedItems.length; let copiedAsIs = false; if (count !== 1) { copiedAsIs = true; } expect(copiedAsIs).toBe(true); }); });