imbajin commented on code in PR #841:
URL: 
https://github.com/apache/hugegraph-toolchain/pull/841#discussion_r4369722165


##########
hugegraph-hubble/hubble-dist/assembly/travis/run_ui_deployment_regressions_impl.js:
##########
@@ -0,0 +1,365 @@
+#!/usr/bin/env node
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+// Browser integration fixture: every API response is mocked. This does not 
verify a live cluster.
+const fs = require('fs');
+const path = require('path');
+const os = require('os');
+const assert = require('node:assert/strict');
+const hubbleRoot = path.resolve(__dirname, '../../..');
+const { chromium } = require(path.join(hubbleRoot, 
'hubble-fe/node_modules/playwright'));
+const base = (process.env.HUBBLE_URL || 
'http://127.0.0.1:3000').replace(/\/+$/, '');
+const output = process.env.HUBBLE_UI_EVIDENCE_DIR || 
fs.mkdtempSync(path.join(os.tmpdir(), 'hubble-deployment-ui-'));
+fs.mkdirSync(output, { recursive: true });
+const checks = [];
+const unexpected = new Set();
+const errors = [];
+let failure = null;
+const spaces = [
+    { name: 'DEFAULT', nickname: '默认图空间', create_time: '2026-10-13', auth: 
false, max_graph_number: 100, cpu_limit: 64, memory_limit: 128, 
compute_cpu_limit: 64, compute_memory_limit: 128, storage_limit: 100, 
storage_used_kb: 10752 },
+    { name: 'soc_graph', nickname: '记忆库', create_time: '2026-10-15', auth: 
false, max_graph_number: 100, cpu_limit: 64, memory_limit: 128, 
compute_cpu_limit: 64, compute_memory_limit: 128, storage_limit: 10, 
storage_used_kb: 252590 },
+];
+const graph = (name, nickname) => ({ name, nickname, status: 'CREATED' });
+// The destination graph must not exist in DEFAULT: stale selector data cannot 
pass.
+const graphsBySpace = {
+    DEFAULT: [graph('hugegraph', '记忆图'), graph('default_only', '默认空间专属图')],
+    soc_graph: [graph('hugegraph', '记忆图'), graph('second', '测试图')],
+};
+const now = 1792757451000;
+const node = (n) => ({ id: `pd-${n}`, name: `PD ${n}`, type: 'PD', role: n === 
1 ? 'LEADER' : 'FOLLOWER', status: 'UP', observed_at: now, metrics: {}, 
metric_statuses: { system: { availability: 'UNAVAILABLE', reason: 
'pd_metrics_unmapped', observed_at: now, fresh: false, stale: false } } });
+const source = { status: 'UP', availability: 'PARTIAL', reason: 
'pd_metrics_unmapped', observed_at: now, last_success_at: now, fresh: false, 
stale: false, diagnostics: { affected_node_count: 3, failed_metric_groups: 
['system'], pd_metric_mapping: { configured_origin: 'http://pd-service:8620', 
status: 'no_match', matched_nodes: 0, discovered_nodes: 3, omitted_members: 0, 
members: [1, 2, 3].map(n => ({ node_id: 'pd-' + n, origin: 'http://pd-' + n + 
':8620', endpoint_valid: true, origin_matches: false, base_path_matches: true 
})) } }, cluster_state: 'Cluster_Not_Ready', data_state: 'PState_Normal' };
+const task = { id: 80001, task_name: 'g.V().limit(100002)', task_type: 
'gremlin', task_status: 'failed', task_create: now - 49000, task_update: now - 
45602 };
+const taskResult = { ...task, task_result: 
'org.apache.hugegraph.exception.LimitExceedException: Task result size 77849617 
exceeded limit 16777216 bytes' };
+function pageResult(records, selected) { return { records, total: 
records.length, current: 1, size: 100, pages: 1, selected: records.find(x => 
x.name === selected) ?? null }; }
+async function setup(browser, auth) {
+    const context = await browser.newContext({ viewport: { width: 1440, 
height: 1000 }, timezoneId: 'Asia/Taipei' });
+    await context.addInitScript(() => localStorage.setItem('languageType', 
'zh-CN'));
+    const state = { auth, loggedIn: false, loginRequests: [], truncation: 
false, pendingDetail: null, taskLookups: 0 };
+    const capabilities = ['graphspaces_read', 'operations_health_read', 
'operations_topology_read', ...(auth ? ['accounts_manage', 
'model_settings_manage'] : [])];
+    await context.route('**/api/v*/**', async (route) => {
+        const req = route.request();
+        const url = new URL(req.url());
+        const p = url.pathname.replace(/^\/api\/v[\d.]+/, '');
+        const method = req.method();
+        let data;
+        if (p === '/config')
+            data = { pd_enabled: true, auth_enabled: auth, 
server_capabilities_verified: true, graph_create_enabled: true, cypher_enabled: 
false };
+        else if (p === '/auth/login') {
+            const credentials = method === 'POST' ? req.postDataJSON() : null;
+            state.loginRequests.push({method, credentials});
+            state.loggedIn = method === 'POST' && credentials?.user_name === 
'smoke_admin' &&
+                credentials?.user_password === 'fixture-only-password';
+            if (!state.loggedIn) {
+                await route.fulfill({status: 401, contentType: 
'application/json',
+                    body: JSON.stringify({status: 401, message: 'Invalid 
fixture login'})});
+                return;
+            }
+            data = { id: 'smoke-admin', user_name: 'smoke_admin', 
user_nickname: '测试管理员', is_superadmin: true };
+        }
+        else if (p === '/auth/context') {
+            const admin = auth && state.loggedIn;
+            data = { mode: auth ? 'AUTH' : 'NON_AUTH', role: admin ? 
'SUPERADMIN' : 'ANONYMOUS', capabilities: auth && !admin ? [] : capabilities, 
actions: { graphspaces: admin ? ['create', 'read', 'update', 'delete'] : auth ? 
[] : ['read'] }, scopes: { all_graphspaces: admin } };
+        }
+        else if (p === '/auth/status')
+            data = {};
+        else if (p === '/auth/users/list')
+            data = { users: [] };
+        else if (p === '/graphspaces/list')
+            data = { graphspaces: spaces.map(x => x.name) };
+        else if (p === '/graphspaces')
+            data = pageResult(spaces, url.searchParams.get('selected'));
+        else if (/^\/graphspaces\/[^/]+$/.test(p)) {
+            const s = spaces.find(x => x.name === p.split('/')[2]);
+            if (method === 'PUT') {
+                Object.assign(s, req.postDataJSON());
+                data = s;
+            }
+            else if (state.pendingDetail && s.name === 'DEFAULT') {
+                const pending = state.pendingDetail;
+                state.pendingDetail = null;
+                await pending;
+                data = s;
+            }
+            else
+                data = s;
+        }
+        else if (p.endsWith('/graphs')) {
+            const graphSpace = p.split('/')[2];
+            assert(graphsBySpace[graphSpace], `Unexpected graph-list scope: 
${graphSpace}`);
+            data = pageResult(graphsBySpace[graphSpace], 
url.searchParams.get('selected'));
+        }
+        else if (p.endsWith('/graph_read_mode'))
+            data = { status: '1' };
+        else if (p === '/vermeer')
+            data = { enable: false };
+        else if (p.endsWith('/text2gql/config'))
+            data = { available: false, custom_available: true, reason: 
'not_configured' };
+        else if (p.endsWith('/schema/edgelabels') || 
p.endsWith('/schema/propertykeys'))
+            data = { records: [] };
+        else if (p.endsWith('/schema/vertexlabels'))
+            data = { records: [{ name: 'Memory', properties: ['name'] }] };
+        else if (p.endsWith('/execute-histories') || 
p.endsWith('/gremlin-collections'))
+            data = { records: [], total: 0 };
+        else if (p.endsWith('/gremlin-query/async-task'))
+            data = { task_id: 80001 };
+        else if (p.endsWith('/gremlin-query'))
+            data = method === 'GET' ? { vertexcount: null, edgecount: null } : 
{ type: 'VERTEX', graph_view: { vertices: [{ id: '1', label: 'Memory', 
properties: { name: 'hello' } }], edges: [] }, table_view: { header: ['id', 
'label', 'properties'], rows: [] }, json_view: { data: [] }, 
default_limit_applied: true, default_limit: 300, ...(state.truncation ? { 
properties_truncated: true, property_preview_limit: 256, 
property_preview_vertex_ids: ['1'], graph_truncated: true } : {}) };
+        else if (p.endsWith('/async-tasks/ids'))

Review Comment:
   ⚠️ Important: Please validate the GraphSpace, graph and requested task IDs 
in these task fixtures, including the submission and result handlers. These 
handlers match only endpoint suffixes: invoking the actual route handler three 
times with `GET 
/api/v1.2/graphspaces/DEFAULT/graphs/default_only/async-tasks/ids?ids=99999` 
returns task `80001` on the third request and leaves `unexpected` empty. The 
scenario below expects `soc_graph/hugegraph` and task `80001`, but checks only 
the visible URL and lookup count. Consequently, stale graph context or an 
incorrect ID in the API requests can still satisfy this regression check. 
Reject mismatched scopes/IDs as unexpected requests so the check fails for that 
regression.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to