From facd54575fcb39ce654877a0ce5e743d5f829fe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=83=E4=B9=98=E5=A6=8D=20=28Xiaoyaner=29?= <258399167+xiaoyaner0201@users.noreply.github.com> Date: Tue, 3 Nov 2026 00:59:18 +0800 Subject: [PATCH] Replace legacy edge fixtures with production assertions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com> --- .../analytics/analytics-umami-example.test.ts | 42 +++------ .../analytics/fixtures/pr1-compose.yaml | 40 --------- .../analytics/fixtures/pr1-nginx.conf | 85 ------------------- 3 files changed, 10 insertions(+), 157 deletions(-) delete mode 100644 web/src/features/analytics/fixtures/pr1-compose.yaml delete mode 100644 web/src/features/analytics/fixtures/pr1-nginx.conf diff --git a/web/src/features/analytics/analytics-umami-example.test.ts b/web/src/features/analytics/analytics-umami-example.test.ts index d25ce5e0c..8495d41d6 100644 --- a/web/src/features/analytics/analytics-umami-example.test.ts +++ b/web/src/features/analytics/analytics-umami-example.test.ts @@ -15,41 +15,19 @@ afterEach(() => { document.head.replaceChildren(); vi.useRealTimers(); vi.restor const website = '4398de8e-40df-4483-9eea-ed213643525f' const intent = { id: 'skill.example.open', event: 'click' } -describe('optional edge deployment defaults', () => { - const nginx = readFileSync('src/features/analytics/fixtures/pr1-nginx.conf', 'utf8') - const compose = readFileSync('src/features/analytics/fixtures/pr1-compose.yaml', 'utf8') - - it('inherits the standard 100 MiB limit on all proxied application routes', () => { - // Tokenize quoted strings as units so braces/semicolons in responses do not - // affect scope. Track blocks instead of accepting a location-only limit. - const tokens = nginx.replace(/#.*$/gm, '').match(/"[^"\n]*"|'[^'\n]*'|[{};]|[^\s{};]+/g) ?? [] - const blocks: string[] = [], statement: string[] = [] - const limits: Array<{ scope: string[]; value: string }> = [] - for (const token of tokens) { - if (token === '{') { blocks.push(statement.join(' ')); statement.length = 0 } - else if (token === '}') { blocks.pop(); statement.length = 0 } - else if (token === ';') { - if (statement[0] === 'client_max_body_size') limits.push({ scope: [...blocks], value: statement[1] }) - statement.length = 0 - } else statement.push(token) - } - expect(limits).toContainEqual({ scope: ['server'], value: '100M' }) - // A collector-only restriction is allowed; application locations must inherit. - expect(limits.filter(limit => limit.scope.some(scope => scope.startsWith('location') && !scope.includes('/_skillhub-umami/')))).toEqual([]) - expect(limits.filter(limit => limit.scope.length === 1)).toEqual([{ scope: ['server'], value: '100M' }]) +describe('production deployment defaults', () => { + it('keeps the 100 MiB application limit at server scope', () => { + const nginx = readFileSync('nginx.conf.template', 'utf8') + expect(nginx).toMatch(/server\s*\{(?:(?!location)[\s\S])*?client_max_body_size 100M;/) }) - - it('defaults to the release Web entry, not the development port', () => { + it('keeps the release Web entry and independent collector networking', () => { const release = readFileSync('../compose.release.yml', 'utf8') + const overlay = readFileSync('../compose.release.analytics.yml', 'utf8') + const collector = readFileSync('../compose.analytics-umami.yml', 'utf8') expect(release).toContain('${WEB_PORT:-80}:80') - expect(compose).toContain('SKILLHUB_WEB_UPSTREAM: ${SKILLHUB_WEB_UPSTREAM:-host.docker.internal:80}') - expect(compose).not.toContain('host.docker.internal:8080') - }) - - it('maps the host gateway on native Linux for the edge service', () => { - const edge = compose.split('\n edge:\n')[1] - expect(edge).toBeDefined() - expect(edge).toContain('extra_hosts: ["host.docker.internal:host-gateway"]') + expect(overlay).toContain(' web:\n networks:\n - default\n - analytics') + expect(collector).toContain(' external: true') + expect(collector).toContain('analytics_postgres_data') }) }) diff --git a/web/src/features/analytics/fixtures/pr1-compose.yaml b/web/src/features/analytics/fixtures/pr1-compose.yaml deleted file mode 100644 index ea521e3f9..000000000 --- a/web/src/features/analytics/fixtures/pr1-compose.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# LOCAL DEMO ONLY. Umami starts with admin / umami: change it immediately. -# The DB password and app secret below are public demo values, NOT production secrets. -# SkillHub is an existing upstream, not built or configured by this demo. -services: - postgres: - image: postgres:16-alpine - environment: - POSTGRES_DB: umami - POSTGRES_USER: umami - POSTGRES_PASSWORD: demo-only-change-me - healthcheck: - test: [CMD-SHELL, pg_isready -U umami -d umami] - interval: 5s - timeout: 3s - retries: 20 - umami: - image: ghcr.io/umami-software/umami:3.4.1 - environment: - DATABASE_URL: postgresql://umami:demo-only-change-me@postgres:5432/umami - APP_SECRET: demo-only-change-this-before-use - depends_on: - postgres: - condition: service_healthy - # Direct admin access is LOCAL setup only; never proxy the admin through edge. - ports: ["127.0.0.1:8300:3000"] - edge: - image: nginx:1.30.6-alpine - # Docker Desktop/OrbStack provide this name; native Linux needs this mapping. - extra_hosts: ["host.docker.internal:host-gateway"] - environment: - SKILLHUB_WEB_UPSTREAM: ${SKILLHUB_WEB_UPSTREAM:-host.docker.internal:80} - UMAMI_UPSTREAM: umami:3000 - NGINX_ENVSUBST_FILTER: ^(SKILLHUB_WEB_UPSTREAM|UMAMI_UPSTREAM)$$ - volumes: - - ./nginx.conf:/etc/nginx/templates/default.conf.template:ro - - ./observe/v1/observer.mjs:/srv/_skillhub-observe/v1/observer.mjs:ro - - ./observe/v1/skillhub-intents.mjs:/srv/_skillhub-observe/v1/skillhub-intents.mjs:ro - - ./observe/v1/umami-adapter.mjs:/srv/_skillhub-observe/v1/umami-adapter.mjs:ro - ports: ["127.0.0.1:8081:8081"] - depends_on: [umami] diff --git a/web/src/features/analytics/fixtures/pr1-nginx.conf b/web/src/features/analytics/fixtures/pr1-nginx.conf deleted file mode 100644 index e64ce3363..000000000 --- a/web/src/features/analytics/fixtures/pr1-nginx.conf +++ /dev/null @@ -1,85 +0,0 @@ -# OPTIONAL deployment-owned edge template. Not included by SkillHub. -# Demo HTTP listener: publish on loopback only, behind your existing HTTPS edge. -# Substitute only SKILLHUB_WEB_UPSTREAM and UMAMI_UPSTREAM (host:port). -# Merge existing TLS, CSP, authentication, host validation and security headers. -upstream skillhub_analytics_web { server ${SKILLHUB_WEB_UPSTREAM}; } -upstream skillhub_analytics_umami { server ${UMAMI_UPSTREAM}; } -server { - listen 8081; - server_name localhost; - # Preserve the standard Web upload limit on every application proxy route. - client_max_body_size 100M; - - # Install ONLY the three .mjs files here, not declarations or other files. - # A single versioned prefix serves the entry AND its relative imports. - location ^~ /_skillhub-observe/v1/ { - root /srv; - try_files $uri =404; - error_page 404 = @module_missing; - autoindex off; - types { application/javascript mjs; } - default_type application/javascript; - add_header X-Content-Type-Options nosniff always; - add_header Cache-Control "public, max-age=31536000, immutable"; - } - location @module_missing { - default_type application/javascript; - add_header X-Content-Type-Options nosniff always; - add_header Cache-Control "no-store" always; - return 404 '// module not found\n'; - } - location /_skillhub-observe/ { return 404; } - - # Expose only two collector endpoints, NEVER Umami's dashboard/admin API. - location = /_skillhub-umami/script.js { - limit_except GET { deny all; } - proxy_pass http://skillhub_analytics_umami/script.js; - proxy_set_header Cookie ""; - proxy_set_header Authorization ""; - proxy_set_header Referer ""; - proxy_hide_header Set-Cookie; - add_header X-Content-Type-Options nosniff always; - } - location = /_skillhub-umami/api/send { - limit_except POST { deny all; } - proxy_pass http://skillhub_analytics_umami/api/send; - proxy_set_header Cookie ""; - proxy_set_header Authorization ""; - proxy_set_header Referer ""; - proxy_hide_header Set-Cookie; - proxy_cache off; - add_header Cache-Control "no-store" always; - } - location /_skillhub-umami/ { return 404; } - - # No filtering or cache-policy changes for API, auth, assets, runtime config, - # registry guides/downloads or health. Preserve original URI (no trailing /). - location ~ ^/(api/|oauth2/|login/oauth2/|\.well-known/|assets/|registry/|runtime-config\.js$|nginx-health) { - proxy_pass http://skillhub_analytics_web; - proxy_set_header Host $http_host; - } - - # Only UI HTML is eligible; sub_filter's default MIME is text/html. - location / { - proxy_pass http://skillhub_analytics_web; - proxy_set_header Host $http_host; - proxy_set_header Accept-Encoding ""; - proxy_set_header If-None-Match ""; - proxy_set_header If-Modified-Since ""; - proxy_cache off; - proxy_hide_header ETag; - proxy_hide_header Last-Modified; - proxy_hide_header Content-MD5; - proxy_hide_header Cache-Control; - proxy_hide_header Expires; - etag off; - if_modified_since off; - add_header Cache-Control "no-store" always; - # Also prevent the browser's HTTP Referer from leaking page paths/queries. - proxy_hide_header Referrer-Policy; - add_header Referrer-Policy "no-referrer" always; - sub_filter_once on; - sub_filter_last_modified off; - sub_filter '' ''; - } -}