2 Commits

Author SHA1 Message Date
ssa
8ca531ca98 Merge pull request 'ai' (#2) from ai into main
Reviewed-on: #2
2026-02-18 23:27:18 +03:00
ssa
22a03735d6 Merge pull request 'ai' (#1) from ai into main
Reviewed-on: #1
2026-02-18 22:44:41 +03:00
6 changed files with 6 additions and 191 deletions

View File

@@ -1,24 +0,0 @@
name: Gitea Actions Demo
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on: [push]
jobs:
Explore-Gitea-Actions:
runs-on: [ubuntu-latest]
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- run: whoami
- run: hostname
- run: node -v
#- name: Check out repository code
#uses: actions/checkout@v6
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ gitea.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."

View File

@@ -87,8 +87,8 @@ docker-compose logs -f
After deployment, access the app at: After deployment, access the app at:
- **Local:** http://localhost:11088 - **Local:** http://localhost
- **Server:** http://your-server-ip:11088 - **Server:** http://your-server-ip
--- ---

View File

@@ -10,7 +10,7 @@ services:
GIT_BRANCH: main GIT_BRANCH: main
container_name: configucci-app container_name: configucci-app
ports: ports:
- "11088:80" - "80:80"
restart: unless-stopped restart: unless-stopped
networks: networks:
- configucci-network - configucci-network

View File

@@ -119,56 +119,6 @@ test.describe('Environment Management', () => {
expect(hostPlaceholderCount).toBe(2); expect(hostPlaceholderCount).toBe(2);
}); });
test('should validate template with unquoted placeholders', async ({ page }) => {
await page.goto('/');
await page.click('button:has-text("Create new")');
await page.waitForTimeout(500);
// Add a parameter
await page.click('button:has-text("✚")');
await page.waitForTimeout(300);
const nameInput = page.locator('input[placeholder="name"]');
const valueInput = page.locator('input[placeholder="value"]');
const addButton = page.locator('button:has-text("✓")');
await nameInput.fill('port');
await valueInput.fill('8080');
await addButton.click();
await page.waitForTimeout(500);
// Go to Content Template and edit with unquoted placeholder
await page.click('a:has-text("Content Template")');
await page.waitForTimeout(300);
await page.click('button:has-text("Edit")');
await page.waitForTimeout(300);
// Fill template with unquoted @port@ placeholder
const textarea = page.locator('textarea');
await textarea.fill('{\n "Host": "@host@",\n "Port": @port@,\n "Url": "http://@host@:@port@/api"\n}');
await page.waitForTimeout(300);
// Check that Save button is enabled (validation passed)
const saveButton = page.locator('button:has-text("Save")');
await expect(saveButton).toBeEnabled();
// Check that there's no JSON error
const errorAlert = page.locator('.alert-danger');
await expect(errorAlert).not.toBeVisible();
// Save the template
await saveButton.click();
await page.waitForTimeout(500);
// Verify it was saved - should be in view mode with Edit button visible
const editButton = page.locator('button:has-text("Edit")');
await expect(editButton).toBeVisible();
// Verify the template content is displayed correctly
const codeContent = page.locator('code');
await expect(codeContent).toBeVisible();
const content = await codeContent.textContent();
expect(content).toContain('@port@');
});
test('should download config file with correct filename', async ({ page }) => { test('should download config file with correct filename', async ({ page }) => {
await page.goto('/'); await page.goto('/');
await page.click('button:has-text("Create new")'); await page.click('button:has-text("Create new")');

View File

@@ -35,10 +35,9 @@ export function ConfigTemplate(props: ConfigTemplateProps) {
} }
function handleSave() { function handleSave() {
// Validate JSON before saving (with placeholder support) // Validate JSON before saving
try { try {
const sanitizedValue = draftContent.replace(/@[^@]+@/g, '1'); JSON.parse(draftContent);
JSON.parse(sanitizedValue);
setJsonError(null); setJsonError(null);
props.onSaved(draftContent); props.onSaved(draftContent);
setMode('view'); setMode('view');
@@ -53,9 +52,7 @@ export function ConfigTemplate(props: ConfigTemplateProps) {
try { try {
if (value.trim()) { if (value.trim()) {
// Replace @placeholders@ with valid JSON values for validation // Replace @placeholders@ with valid JSON values for validation
// Strategy: Replace ALL @...@ patterns with "1" (valid for both string and numeric contexts) const sanitizedValue = value.replace(/"@?(\w+)@?"/g, '"__PLACEHOLDER__"');
const sanitizedValue = value.replace(/@[^@]+@/g, '1');
JSON.parse(sanitizedValue); JSON.parse(sanitizedValue);
setJsonError(null); setJsonError(null);
} else { } else {

View File

@@ -1,108 +0,0 @@
import { describe, it, expect } from 'vitest';
describe('JSON Validation with @placeholders@', () => {
/**
* Helper function that mimics the validation logic in ConfigTemplate.tsx
*/
function validateJsonWithPlaceholders(value: string): { valid: boolean; error?: string } {
try {
if (!value.trim()) {
return { valid: true };
}
// This is the current implementation from ConfigTemplate.tsx
// Replace ALL @...@ patterns with "1" (valid for both string and numeric contexts)
const sanitizedValue = value.replace(/@[^@]+@/g, '1');
JSON.parse(sanitizedValue);
return { valid: true };
} catch (e) {
return { valid: false, error: (e as Error).message };
}
}
it('should validate quoted placeholders', () => {
const json = `{
"Host": "@host@",
"Port": "@port@"
}`;
const result = validateJsonWithPlaceholders(json);
expect(result.valid).toBe(true);
});
it('should validate unquoted placeholders', () => {
const json = `{
"Host": "@host@",
"Port": @port@
}`;
const result = validateJsonWithPlaceholders(json);
expect(result.valid).toBe(true);
});
it('should validate mixed quoted and unquoted placeholders', () => {
const json = `{
"Host": "@host@",
"Port": @port@,
"ApiPath": "@host@:@port@/v1/data",
"MessageBroker": {
"hosts": @MessageBrokerHosts@
}
}`;
const result = validateJsonWithPlaceholders(json);
expect(result.valid).toBe(true);
});
it('should validate placeholders inside strings (URLs)', () => {
const json = `{
"ApiUrl": "http://@host@:@port@/api"
}`;
const result = validateJsonWithPlaceholders(json);
expect(result.valid).toBe(true);
});
it('should validate complex real-world template', () => {
const json = `{
"Host": "@host@",
"Port": @port@,
"ApiPath": "@host@:@port@/v1/data",
"MessageBroker": {
"hosts": @MessageBrokerHosts@
},
"basePath": "./@env_name@/in",
"NoParam": "@no_param@"
}`;
const result = validateJsonWithPlaceholders(json);
expect(result.valid).toBe(true);
});
it('should reject invalid JSON structure', () => {
const json = `{
"Host": "@host@",
"Port": @port@
"Missing": "comma"
}`;
const result = validateJsonWithPlaceholders(json);
expect(result.valid).toBe(false);
expect(result.error).toContain('Expected');
});
it('should handle empty value', () => {
const result = validateJsonWithPlaceholders('');
expect(result.valid).toBe(true);
});
it('should handle whitespace only', () => {
const result = validateJsonWithPlaceholders(' \n\t ');
expect(result.valid).toBe(true);
});
});