fix(build): runtime detection, WordPress support, Laravel inline configs
- detectRuntime reads parent directory not zip file itself - Full WordPress Dockerfile with custom entrypoint for wp-content merging - Laravel: inline nginx.conf and supervisord.conf generation - composer.lock made optional - ConfigMap cleanup in finally block - Unit tests for WordPress build flow
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user