feat(portal): add logical group packaging config
This commit is contained in:
@@ -545,6 +545,36 @@
|
||||
<textarea id="group-next-step-hint" placeholder="例如:先创建测试 Key,再按推荐模型发起第一次请求。"></textarea>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field-grid two" style="margin-top:12px;">
|
||||
<label>
|
||||
visibility_scope
|
||||
<select id="group-visibility-scope">
|
||||
<option value="public">public</option>
|
||||
<option value="login_required">login_required</option>
|
||||
<option value="entitled_only">entitled_only</option>
|
||||
<option value="hidden">hidden</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
package_tier
|
||||
<select id="group-package-tier">
|
||||
<option value="free">free</option>
|
||||
<option value="standard">standard</option>
|
||||
<option value="pro">pro</option>
|
||||
<option value="enterprise">enterprise</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field-grid two" style="margin-top:12px;">
|
||||
<label>
|
||||
purchase_cta_label
|
||||
<input id="group-purchase-cta-label" type="text" placeholder="例如:升级到 Pro">
|
||||
</label>
|
||||
<label>
|
||||
purchase_cta_url
|
||||
<input id="group-purchase-cta-url" type="text" placeholder="例如:https://sub.tksea.top/portal/upgrade/pro">
|
||||
</label>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="primary" id="create-group-btn" type="button">创建分组</button>
|
||||
<button class="secondary" id="update-group-btn" type="button">更新分组</button>
|
||||
@@ -708,6 +738,10 @@
|
||||
const groupUsageScenarioInput = document.getElementById("group-usage-scenario");
|
||||
const groupRecommendationInput = document.getElementById("group-recommendation");
|
||||
const groupNextStepHintInput = document.getElementById("group-next-step-hint");
|
||||
const groupVisibilityScopeInput = document.getElementById("group-visibility-scope");
|
||||
const groupPackageTierInput = document.getElementById("group-package-tier");
|
||||
const groupPurchaseCTALabelInput = document.getElementById("group-purchase-cta-label");
|
||||
const groupPurchaseCTAURLInput = document.getElementById("group-purchase-cta-url");
|
||||
const groupRoutePolicyInput = document.getElementById("group-route-policy");
|
||||
const groupStickyModeInput = document.getElementById("group-sticky-mode");
|
||||
const groupConversationTTLInput = document.getElementById("group-conversation-ttl");
|
||||
@@ -888,6 +922,10 @@
|
||||
usage_scenario: groupUsageScenarioInput.value.trim(),
|
||||
recommendation: groupRecommendationInput.value.trim(),
|
||||
next_step_hint: groupNextStepHintInput.value.trim(),
|
||||
visibility_scope: groupVisibilityScopeInput.value,
|
||||
package_tier: groupPackageTierInput.value,
|
||||
purchase_cta_label: groupPurchaseCTALabelInput.value.trim(),
|
||||
purchase_cta_url: groupPurchaseCTAURLInput.value.trim(),
|
||||
route_policy: groupRoutePolicyInput.value,
|
||||
sticky_mode: groupStickyModeInput.value,
|
||||
conversation_ttl_seconds: Number(groupConversationTTLInput.value || "0"),
|
||||
@@ -919,6 +957,10 @@
|
||||
groupUsageScenarioInput.value = group?.usage_scenario || "";
|
||||
groupRecommendationInput.value = group?.recommendation || "";
|
||||
groupNextStepHintInput.value = group?.next_step_hint || "";
|
||||
groupVisibilityScopeInput.value = group?.visibility_scope || "public";
|
||||
groupPackageTierInput.value = group?.package_tier || "standard";
|
||||
groupPurchaseCTALabelInput.value = group?.purchase_cta_label || "";
|
||||
groupPurchaseCTAURLInput.value = group?.purchase_cta_url || "";
|
||||
groupRoutePolicyInput.value = group?.route_policy || "priority";
|
||||
groupStickyModeInput.value = group?.sticky_mode || "conversation_preferred";
|
||||
groupConversationTTLInput.value = String(group?.conversation_ttl_seconds || 7200);
|
||||
|
||||
@@ -528,6 +528,23 @@
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.cta-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(15, 118, 110, 0.24);
|
||||
background: rgba(15, 118, 110, 0.08);
|
||||
color: var(--teal);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
.cta-link:hover {
|
||||
background: rgba(15, 118, 110, 0.14);
|
||||
}
|
||||
.toast {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
@@ -1106,6 +1123,38 @@
|
||||
: [];
|
||||
}
|
||||
|
||||
function logicalGroupVisibilityScope(group) {
|
||||
return String(group && group.visibility_scope ? group.visibility_scope : "public").trim() || "public";
|
||||
}
|
||||
|
||||
function logicalGroupPackageTier(group) {
|
||||
return String(group && group.package_tier ? group.package_tier : "standard").trim() || "standard";
|
||||
}
|
||||
|
||||
function logicalGroupPurchaseCTA(group) {
|
||||
return {
|
||||
label: String(group && group.purchase_cta_label ? group.purchase_cta_label : "").trim(),
|
||||
url: String(group && group.purchase_cta_url ? group.purchase_cta_url : "").trim()
|
||||
};
|
||||
}
|
||||
|
||||
function logicalGroupVisibleForViewer(row) {
|
||||
const scope = logicalGroupVisibilityScope(row.logicalGroup);
|
||||
if (scope === "hidden") {
|
||||
return false;
|
||||
}
|
||||
if (scope === "public") {
|
||||
return true;
|
||||
}
|
||||
if (scope === "login_required") {
|
||||
return !!state.user;
|
||||
}
|
||||
if (scope === "entitled_only") {
|
||||
return !!state.user && (row.stateKey === "active" || row.stateKey === "granted" || row.stateKey === "ambiguous");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function legacyCompatibilityCandidates(group) {
|
||||
const portalModels = new Set(portalLogicalGroupModels(group));
|
||||
if (!portalModels.size) {
|
||||
@@ -1125,7 +1174,8 @@
|
||||
enabledCandidates
|
||||
};
|
||||
});
|
||||
rows.sort((left, right) => {
|
||||
const filtered = rows.filter((row) => logicalGroupVisibleForViewer(row));
|
||||
filtered.sort((left, right) => {
|
||||
const leftActive = Number(left.logicalGroup.active_route_count || 0);
|
||||
const rightActive = Number(right.logicalGroup.active_route_count || 0);
|
||||
if (leftActive !== rightActive) {
|
||||
@@ -1136,7 +1186,7 @@
|
||||
"zh-CN"
|
||||
);
|
||||
});
|
||||
return rows;
|
||||
return filtered;
|
||||
}
|
||||
|
||||
function activeSubscriptionItems() {
|
||||
@@ -1221,11 +1271,15 @@
|
||||
: "pending";
|
||||
const compatibilityNames = (row.candidates || []).map((candidate) => candidate.title).join(" / ") || "无兼容线路";
|
||||
const models = portalLogicalGroupModels(group).join(", ") || "--";
|
||||
const packageTier = logicalGroupPackageTier(group);
|
||||
const visibilityScope = logicalGroupVisibilityScope(group);
|
||||
return (
|
||||
'<article class="group-card ' + (row.stateKey === "active" ? "active" : (row.stateKey === "catalog_only" ? "neutral" : "pending")) + '">' +
|
||||
'<h4>' + escapeHTML(group.display_name || group.logicalGroupID || "未命名逻辑分组") + '</h4>' +
|
||||
'<div class="group-meta">' +
|
||||
'<span class="badge strong ' + stateBadgeClass + '">' + escapeHTML(row.stateText) + '</span>' +
|
||||
'<span class="badge">' + escapeHTML(packageTier) + '</span>' +
|
||||
'<span class="badge">' + escapeHTML(visibilityScope) + '</span>' +
|
||||
'<span class="badge">兼容线路 ' + String(row.candidates.length) + '</span>' +
|
||||
'<span class="badge">活跃订阅 ' + String(row.matchingActiveSubscriptions.length) + '</span>' +
|
||||
'<span class="badge">已有 Key ' + String(row.matchingKeys.length) + '</span>' +
|
||||
@@ -1277,10 +1331,13 @@
|
||||
models,
|
||||
stateText: row.stateText,
|
||||
stateKey: row.stateKey,
|
||||
packageTier: logicalGroupPackageTier(group),
|
||||
visibilityScope: logicalGroupVisibilityScope(group),
|
||||
scenario: configuredScenario || guidance.scenario,
|
||||
recommendation: configuredRecommendation || guidance.recommendation,
|
||||
nextStep: configuredNextStep || defaultNextStep,
|
||||
compatibility,
|
||||
cta: logicalGroupPurchaseCTA(group),
|
||||
stickyMode: group.sticky_mode || "conversation_preferred",
|
||||
routePolicy: group.route_policy || "priority"
|
||||
};
|
||||
@@ -1304,6 +1361,8 @@
|
||||
'<article class="guide-card">' +
|
||||
'<div class="group-meta">' +
|
||||
'<span class="badge strong ' + badgeClass + '">' + escapeHTML(guide.stateText) + '</span>' +
|
||||
'<span class="badge">' + escapeHTML(guide.packageTier) + '</span>' +
|
||||
'<span class="badge">' + escapeHTML(guide.visibilityScope) + '</span>' +
|
||||
'<span class="badge mono">' + escapeHTML(guide.logicalGroupID) + '</span>' +
|
||||
'</div>' +
|
||||
'<h4>' + escapeHTML(guide.title) + '</h4>' +
|
||||
@@ -1315,6 +1374,9 @@
|
||||
'<div class="guide-line"><strong>兼容线路:</strong>' + escapeHTML(guide.compatibility) + '</div>' +
|
||||
'<div class="guide-line"><strong>路由策略:</strong><span class="mono">' + escapeHTML(guide.routePolicy) + '</span> / <span class="mono">' + escapeHTML(guide.stickyMode) + '</span></div>' +
|
||||
'</div>' +
|
||||
((guide.cta.label && guide.cta.url && guide.stateKey !== "active")
|
||||
? ('<a class="cta-link" href="' + escapeHTML(guide.cta.url) + '" target="_blank" rel="noreferrer">' + escapeHTML(guide.cta.label) + '</a>')
|
||||
: '') +
|
||||
'</article>'
|
||||
);
|
||||
}).join("");
|
||||
@@ -1392,6 +1454,8 @@
|
||||
const group = row.logicalGroup;
|
||||
const presentation = getPresentationStatus(row);
|
||||
const models = portalLogicalGroupModels(group);
|
||||
const packageTier = logicalGroupPackageTier(group);
|
||||
const visibilityScope = logicalGroupVisibilityScope(group);
|
||||
const modelsHTML = models.length
|
||||
? "<ul class=\"group-models\">" + models.map((model) => "<li><span class=\"mono\">" + escapeHTML(model) + "</span></li>").join("") + "</ul>"
|
||||
: "<div class=\"group-note\">当前尚未登记公开模型。</div>";
|
||||
@@ -1409,6 +1473,8 @@
|
||||
'<div class="group-meta">' +
|
||||
'<span class="badge">logical group</span>' +
|
||||
'<span class="badge mono">' + escapeHTML(group.logical_group_id || "--") + '</span>' +
|
||||
'<span class="badge">' + escapeHTML(packageTier) + '</span>' +
|
||||
'<span class="badge">' + escapeHTML(visibilityScope) + '</span>' +
|
||||
'<span class="badge">' + escapeHTML(group.route_policy || "priority") + '</span>' +
|
||||
'<span class="badge">' + escapeHTML(group.sticky_mode || "conversation_preferred") + '</span>' +
|
||||
'<span class="badge strong ' + presentation.cls + '">' + escapeHTML(presentation.text) + '</span>' +
|
||||
@@ -1456,11 +1522,14 @@
|
||||
? "你的账号暂未开通兼容宿主线路,当前只能浏览目录,不能直接申请测试 Key。"
|
||||
: "当前逻辑分组尚未建立自动申请测试 Key 的兼容线路。";
|
||||
$("create-key-btn").disabled = !canCreate;
|
||||
const cta = logicalGroupPurchaseCTA(row.logicalGroup);
|
||||
$("selection-summary").innerHTML = [
|
||||
'<div><strong>' + escapeHTML(row.logicalGroup.display_name || row.logicalGroup.logical_group_id || "未命名逻辑分组") + '</strong> / <span class="mono">' + escapeHTML(logicalGroupID) + '</span></div>',
|
||||
'<div class="mono">公开模型: ' + escapeHTML(models.join(", ") || "--") + '</div>',
|
||||
'<div class="mono">package_tier = ' + escapeHTML(logicalGroupPackageTier(row.logicalGroup)) + ' / visibility_scope = ' + escapeHTML(logicalGroupVisibilityScope(row.logicalGroup)) + '</div>',
|
||||
'<div class="mono">route_policy = ' + escapeHTML(row.logicalGroup.route_policy || "priority") + ' / sticky_mode = ' + escapeHTML(row.logicalGroup.sticky_mode || "conversation_preferred") + '</div>',
|
||||
'<div>' + escapeHTML(compatibility) + '</div>'
|
||||
'<div>' + escapeHTML(compatibility) + '</div>',
|
||||
(cta.label && cta.url ? ('<div><a class="cta-link" href="' + escapeHTML(cta.url) + '" target="_blank" rel="noreferrer">' + escapeHTML(cta.label) + '</a></div>') : '')
|
||||
].join("");
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@ type CreateLogicalGroupRequest struct {
|
||||
UsageScenario string `json:"usage_scenario,omitempty"`
|
||||
Recommendation string `json:"recommendation,omitempty"`
|
||||
NextStepHint string `json:"next_step_hint,omitempty"`
|
||||
VisibilityScope string `json:"visibility_scope,omitempty"`
|
||||
PackageTier string `json:"package_tier,omitempty"`
|
||||
PurchaseCTALabel string `json:"purchase_cta_label,omitempty"`
|
||||
PurchaseCTAURL string `json:"purchase_cta_url,omitempty"`
|
||||
RoutePolicy string `json:"route_policy,omitempty"`
|
||||
StickyMode string `json:"sticky_mode,omitempty"`
|
||||
ConversationTTLSeconds int `json:"conversation_ttl_seconds,omitempty"`
|
||||
@@ -35,6 +39,10 @@ type UpdateLogicalGroupRequest struct {
|
||||
UsageScenario string `json:"usage_scenario,omitempty"`
|
||||
Recommendation string `json:"recommendation,omitempty"`
|
||||
NextStepHint string `json:"next_step_hint,omitempty"`
|
||||
VisibilityScope string `json:"visibility_scope,omitempty"`
|
||||
PackageTier string `json:"package_tier,omitempty"`
|
||||
PurchaseCTALabel string `json:"purchase_cta_label,omitempty"`
|
||||
PurchaseCTAURL string `json:"purchase_cta_url,omitempty"`
|
||||
RoutePolicy string `json:"route_policy,omitempty"`
|
||||
StickyMode string `json:"sticky_mode,omitempty"`
|
||||
ConversationTTLSeconds int `json:"conversation_ttl_seconds,omitempty"`
|
||||
@@ -51,6 +59,10 @@ type LogicalGroupInfo struct {
|
||||
UsageScenario string `json:"usage_scenario,omitempty"`
|
||||
Recommendation string `json:"recommendation,omitempty"`
|
||||
NextStepHint string `json:"next_step_hint,omitempty"`
|
||||
VisibilityScope string `json:"visibility_scope,omitempty"`
|
||||
PackageTier string `json:"package_tier,omitempty"`
|
||||
PurchaseCTALabel string `json:"purchase_cta_label,omitempty"`
|
||||
PurchaseCTAURL string `json:"purchase_cta_url,omitempty"`
|
||||
RoutePolicy string `json:"route_policy,omitempty"`
|
||||
StickyMode string `json:"sticky_mode,omitempty"`
|
||||
ConversationTTLSeconds int `json:"conversation_ttl_seconds,omitempty"`
|
||||
@@ -445,6 +457,10 @@ func buildUpdateLogicalGroupAction(sqliteDSN string) func(context.Context, Updat
|
||||
UsageScenario: req.UsageScenario,
|
||||
Recommendation: req.Recommendation,
|
||||
NextStepHint: req.NextStepHint,
|
||||
VisibilityScope: req.VisibilityScope,
|
||||
PackageTier: req.PackageTier,
|
||||
PurchaseCTALabel: req.PurchaseCTALabel,
|
||||
PurchaseCTAURL: req.PurchaseCTAURL,
|
||||
RoutePolicy: req.RoutePolicy,
|
||||
StickyMode: req.StickyMode,
|
||||
ConversationTTLSeconds: req.ConversationTTLSeconds,
|
||||
@@ -698,6 +714,10 @@ func logicalGroupRequestToRow(req CreateLogicalGroupRequest) sqlite.LogicalGroup
|
||||
UsageScenario: strings.TrimSpace(req.UsageScenario),
|
||||
Recommendation: strings.TrimSpace(req.Recommendation),
|
||||
NextStepHint: strings.TrimSpace(req.NextStepHint),
|
||||
VisibilityScope: strings.TrimSpace(req.VisibilityScope),
|
||||
PackageTier: strings.TrimSpace(req.PackageTier),
|
||||
PurchaseCTALabel: strings.TrimSpace(req.PurchaseCTALabel),
|
||||
PurchaseCTAURL: strings.TrimSpace(req.PurchaseCTAURL),
|
||||
RoutePolicy: strings.TrimSpace(req.RoutePolicy),
|
||||
StickyMode: strings.TrimSpace(req.StickyMode),
|
||||
ConversationTTLSeconds: req.ConversationTTLSeconds,
|
||||
@@ -763,6 +783,10 @@ func logicalGroupRowToInfo(group sqlite.LogicalGroup, models []LogicalGroupModel
|
||||
UsageScenario: group.UsageScenario,
|
||||
Recommendation: group.Recommendation,
|
||||
NextStepHint: group.NextStepHint,
|
||||
VisibilityScope: group.VisibilityScope,
|
||||
PackageTier: group.PackageTier,
|
||||
PurchaseCTALabel: group.PurchaseCTALabel,
|
||||
PurchaseCTAURL: group.PurchaseCTAURL,
|
||||
RoutePolicy: group.RoutePolicy,
|
||||
StickyMode: group.StickyMode,
|
||||
ConversationTTLSeconds: group.ConversationTTLSeconds,
|
||||
|
||||
@@ -19,20 +19,31 @@ func TestAPICreateLogicalGroupReturnsCreated(t *testing.T) {
|
||||
if req.UsageScenario != "适合统一 GPT 产品入口" {
|
||||
t.Fatalf("UsageScenario = %q, want configured guidance", req.UsageScenario)
|
||||
}
|
||||
if req.VisibilityScope != "login_required" || req.PackageTier != "pro" || req.PurchaseCTALabel != "升级到 Pro" || req.PurchaseCTAURL != "https://sub.tksea.top/portal/upgrade/pro" {
|
||||
t.Fatalf("packaging fields = %+v, want configured packaging", req)
|
||||
}
|
||||
return LogicalGroupInfo{
|
||||
LogicalGroupID: req.LogicalGroupID,
|
||||
DisplayName: req.DisplayName,
|
||||
Status: req.Status,
|
||||
UsageScenario: req.UsageScenario,
|
||||
LogicalGroupID: req.LogicalGroupID,
|
||||
DisplayName: req.DisplayName,
|
||||
Status: req.Status,
|
||||
UsageScenario: req.UsageScenario,
|
||||
VisibilityScope: req.VisibilityScope,
|
||||
PackageTier: req.PackageTier,
|
||||
PurchaseCTALabel: req.PurchaseCTALabel,
|
||||
PurchaseCTAURL: req.PurchaseCTAURL,
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
|
||||
request := httptestRequest(t, http.MethodPost, "/api/logical-groups", map[string]any{
|
||||
"logical_group_id": "gpt-shared",
|
||||
"display_name": "GPT Shared",
|
||||
"status": "active",
|
||||
"usage_scenario": "适合统一 GPT 产品入口",
|
||||
"logical_group_id": "gpt-shared",
|
||||
"display_name": "GPT Shared",
|
||||
"status": "active",
|
||||
"usage_scenario": "适合统一 GPT 产品入口",
|
||||
"visibility_scope": "login_required",
|
||||
"package_tier": "pro",
|
||||
"purchase_cta_label": "升级到 Pro",
|
||||
"purchase_cta_url": "https://sub.tksea.top/portal/upgrade/pro",
|
||||
}, "secret-token")
|
||||
response := httptestRecorder(handler, request)
|
||||
assertStatusCode(t, response, http.StatusCreated)
|
||||
@@ -46,13 +57,17 @@ func TestAPIGetLogicalGroupReturnsAggregatedItem(t *testing.T) {
|
||||
t.Fatalf("groupID = %q, want gpt-shared", groupID)
|
||||
}
|
||||
return LogicalGroupInfo{
|
||||
LogicalGroupID: groupID,
|
||||
DisplayName: "GPT Shared",
|
||||
Status: "active",
|
||||
UsageScenario: "适合统一 GPT 产品入口",
|
||||
Recommendation: "优先使用 gpt-5.4",
|
||||
NextStepHint: "先创建测试 Key",
|
||||
Models: []LogicalGroupModelInfo{{PublicModel: "gpt-5.4", Status: "active"}},
|
||||
LogicalGroupID: groupID,
|
||||
DisplayName: "GPT Shared",
|
||||
Status: "active",
|
||||
UsageScenario: "适合统一 GPT 产品入口",
|
||||
Recommendation: "优先使用 gpt-5.4",
|
||||
NextStepHint: "先创建测试 Key",
|
||||
VisibilityScope: "login_required",
|
||||
PackageTier: "pro",
|
||||
PurchaseCTALabel: "升级到 Pro",
|
||||
PurchaseCTAURL: "https://sub.tksea.top/portal/upgrade/pro",
|
||||
Models: []LogicalGroupModelInfo{{PublicModel: "gpt-5.4", Status: "active"}},
|
||||
Routes: []LogicalGroupRouteInfo{{
|
||||
RouteID: "asxs",
|
||||
LogicalGroupID: groupID,
|
||||
@@ -94,6 +109,9 @@ func TestAPIGetLogicalGroupReturnsAggregatedItem(t *testing.T) {
|
||||
if group["usage_scenario"] != "适合统一 GPT 产品入口" || group["recommendation"] != "优先使用 gpt-5.4" || group["next_step_hint"] != "先创建测试 Key" {
|
||||
t.Fatalf("group guidance = %#v, want configured guidance fields", group)
|
||||
}
|
||||
if group["visibility_scope"] != "login_required" || group["package_tier"] != "pro" || group["purchase_cta_label"] != "升级到 Pro" || group["purchase_cta_url"] != "https://sub.tksea.top/portal/upgrade/pro" {
|
||||
t.Fatalf("group packaging = %#v, want configured packaging fields", group)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPICreateLogicalGroupRouteUsesPathGroupID(t *testing.T) {
|
||||
@@ -159,12 +177,16 @@ func TestNewActionSetLogicalGroupCRUDFlow(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
createdGroup, err := actions.CreateLogicalGroup(ctx, CreateLogicalGroupRequest{
|
||||
LogicalGroupID: "gpt-shared",
|
||||
DisplayName: "GPT Shared",
|
||||
Status: "active",
|
||||
UsageScenario: "适合统一 GPT 产品入口",
|
||||
Recommendation: "优先使用 gpt-5.4",
|
||||
NextStepHint: "先创建测试 Key",
|
||||
LogicalGroupID: "gpt-shared",
|
||||
DisplayName: "GPT Shared",
|
||||
Status: "active",
|
||||
UsageScenario: "适合统一 GPT 产品入口",
|
||||
Recommendation: "优先使用 gpt-5.4",
|
||||
NextStepHint: "先创建测试 Key",
|
||||
VisibilityScope: "login_required",
|
||||
PackageTier: "pro",
|
||||
PurchaseCTALabel: "升级到 Pro",
|
||||
PurchaseCTAURL: "https://sub.tksea.top/portal/upgrade/pro",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateLogicalGroup() error = %v", err)
|
||||
@@ -175,6 +197,9 @@ func TestNewActionSetLogicalGroupCRUDFlow(t *testing.T) {
|
||||
if createdGroup.UsageScenario != "适合统一 GPT 产品入口" || createdGroup.Recommendation != "优先使用 gpt-5.4" || createdGroup.NextStepHint != "先创建测试 Key" {
|
||||
t.Fatalf("CreateLogicalGroup() guidance = %+v, want configured guidance fields", createdGroup)
|
||||
}
|
||||
if createdGroup.VisibilityScope != "login_required" || createdGroup.PackageTier != "pro" || createdGroup.PurchaseCTALabel != "升级到 Pro" || createdGroup.PurchaseCTAURL != "https://sub.tksea.top/portal/upgrade/pro" {
|
||||
t.Fatalf("CreateLogicalGroup() packaging = %+v, want configured packaging fields", createdGroup)
|
||||
}
|
||||
|
||||
if _, err := actions.CreateLogicalGroupModel(ctx, CreateLogicalGroupModelRequest{
|
||||
LogicalGroupID: "gpt-shared",
|
||||
@@ -219,14 +244,21 @@ func TestNewActionSetLogicalGroupCRUDFlow(t *testing.T) {
|
||||
if group.UsageScenario != "适合统一 GPT 产品入口" || group.Recommendation != "优先使用 gpt-5.4" || group.NextStepHint != "先创建测试 Key" {
|
||||
t.Fatalf("GetLogicalGroup() guidance = %+v, want configured guidance fields", group)
|
||||
}
|
||||
if group.VisibilityScope != "login_required" || group.PackageTier != "pro" || group.PurchaseCTALabel != "升级到 Pro" || group.PurchaseCTAURL != "https://sub.tksea.top/portal/upgrade/pro" {
|
||||
t.Fatalf("GetLogicalGroup() packaging = %+v, want configured packaging fields", group)
|
||||
}
|
||||
|
||||
if _, err := actions.UpdateLogicalGroup(ctx, UpdateLogicalGroupRequest{
|
||||
LogicalGroupID: "gpt-shared",
|
||||
DisplayName: "GPT Shared Updated",
|
||||
Status: "paused",
|
||||
UsageScenario: "适合升级后的 GPT 产品入口",
|
||||
Recommendation: "先验证高质量推理链路",
|
||||
NextStepHint: "升级后重新申请测试 Key",
|
||||
LogicalGroupID: "gpt-shared",
|
||||
DisplayName: "GPT Shared Updated",
|
||||
Status: "paused",
|
||||
UsageScenario: "适合升级后的 GPT 产品入口",
|
||||
Recommendation: "先验证高质量推理链路",
|
||||
NextStepHint: "升级后重新申请测试 Key",
|
||||
VisibilityScope: "entitled_only",
|
||||
PackageTier: "enterprise",
|
||||
PurchaseCTALabel: "联系销售升级",
|
||||
PurchaseCTAURL: "https://sub.tksea.top/portal/contact-sales",
|
||||
}); err != nil {
|
||||
t.Fatalf("UpdateLogicalGroup() error = %v", err)
|
||||
}
|
||||
@@ -254,6 +286,9 @@ func TestNewActionSetLogicalGroupCRUDFlow(t *testing.T) {
|
||||
if groups[0].UsageScenario != "适合升级后的 GPT 产品入口" || groups[0].Recommendation != "先验证高质量推理链路" || groups[0].NextStepHint != "升级后重新申请测试 Key" {
|
||||
t.Fatalf("ListLogicalGroups() guidance = %+v, want updated guidance fields", groups[0])
|
||||
}
|
||||
if groups[0].VisibilityScope != "entitled_only" || groups[0].PackageTier != "enterprise" || groups[0].PurchaseCTALabel != "联系销售升级" || groups[0].PurchaseCTAURL != "https://sub.tksea.top/portal/contact-sales" {
|
||||
t.Fatalf("ListLogicalGroups() packaging = %+v, want updated packaging fields", groups[0])
|
||||
}
|
||||
|
||||
routeModels, err := actions.ListLogicalGroupRouteModels(ctx, ListLogicalGroupRouteModelsRequest{
|
||||
LogicalGroupID: "gpt-shared",
|
||||
|
||||
@@ -16,6 +16,10 @@ type PortalLogicalGroupInfo struct {
|
||||
UsageScenario string `json:"usage_scenario,omitempty"`
|
||||
Recommendation string `json:"recommendation,omitempty"`
|
||||
NextStepHint string `json:"next_step_hint,omitempty"`
|
||||
VisibilityScope string `json:"visibility_scope,omitempty"`
|
||||
PackageTier string `json:"package_tier,omitempty"`
|
||||
PurchaseCTALabel string `json:"purchase_cta_label,omitempty"`
|
||||
PurchaseCTAURL string `json:"purchase_cta_url,omitempty"`
|
||||
Status string `json:"status"`
|
||||
StickyMode string `json:"sticky_mode,omitempty"`
|
||||
RoutePolicy string `json:"route_policy,omitempty"`
|
||||
@@ -165,6 +169,10 @@ func buildPortalLogicalGroupInfo(ctx context.Context, store *sqlite.DB, group sq
|
||||
UsageScenario: group.UsageScenario,
|
||||
Recommendation: group.Recommendation,
|
||||
NextStepHint: group.NextStepHint,
|
||||
VisibilityScope: group.VisibilityScope,
|
||||
PackageTier: group.PackageTier,
|
||||
PurchaseCTALabel: group.PurchaseCTALabel,
|
||||
PurchaseCTAURL: group.PurchaseCTAURL,
|
||||
Status: group.Status,
|
||||
StickyMode: group.StickyMode,
|
||||
RoutePolicy: group.RoutePolicy,
|
||||
|
||||
@@ -18,6 +18,10 @@ func TestAPIListPortalLogicalGroups(t *testing.T) {
|
||||
UsageScenario: "适合统一 GPT 产品入口",
|
||||
Recommendation: "优先使用 gpt-5.4",
|
||||
NextStepHint: "先创建测试 Key",
|
||||
VisibilityScope: "login_required",
|
||||
PackageTier: "pro",
|
||||
PurchaseCTALabel: "升级到 Pro",
|
||||
PurchaseCTAURL: "https://sub.tksea.top/portal/upgrade/pro",
|
||||
Status: "active",
|
||||
RouteCount: 2,
|
||||
ActiveRouteCount: 1,
|
||||
@@ -43,6 +47,9 @@ func TestAPIListPortalLogicalGroups(t *testing.T) {
|
||||
if listPayload.LogicalGroups[0].UsageScenario != "适合统一 GPT 产品入口" || listPayload.LogicalGroups[0].Recommendation != "优先使用 gpt-5.4" || listPayload.LogicalGroups[0].NextStepHint != "先创建测试 Key" {
|
||||
t.Fatalf("portal logical groups guidance = %+v", listPayload.LogicalGroups[0])
|
||||
}
|
||||
if listPayload.LogicalGroups[0].VisibilityScope != "login_required" || listPayload.LogicalGroups[0].PackageTier != "pro" || listPayload.LogicalGroups[0].PurchaseCTALabel != "升级到 Pro" || listPayload.LogicalGroups[0].PurchaseCTAURL != "https://sub.tksea.top/portal/upgrade/pro" {
|
||||
t.Fatalf("portal logical groups packaging = %+v", listPayload.LogicalGroups[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIGetPortalLogicalGroupModels(t *testing.T) {
|
||||
@@ -89,6 +96,10 @@ func TestNewActionSetPortalLogicalGroups(t *testing.T) {
|
||||
UsageScenario: "适合统一 GPT 产品入口",
|
||||
Recommendation: "优先使用 gpt-5.4",
|
||||
NextStepHint: "先创建测试 Key",
|
||||
VisibilityScope: "login_required",
|
||||
PackageTier: "pro",
|
||||
PurchaseCTALabel: "升级到 Pro",
|
||||
PurchaseCTAURL: "https://sub.tksea.top/portal/upgrade/pro",
|
||||
RoutePolicy: "priority",
|
||||
StickyMode: "conversation_preferred",
|
||||
ConversationTTLSeconds: 7200,
|
||||
@@ -170,6 +181,9 @@ func TestNewActionSetPortalLogicalGroups(t *testing.T) {
|
||||
if group.UsageScenario != "适合统一 GPT 产品入口" || group.Recommendation != "优先使用 gpt-5.4" || group.NextStepHint != "先创建测试 Key" {
|
||||
t.Fatalf("GetPortalLogicalGroup() guidance = %+v", group)
|
||||
}
|
||||
if group.VisibilityScope != "login_required" || group.PackageTier != "pro" || group.PurchaseCTALabel != "升级到 Pro" || group.PurchaseCTAURL != "https://sub.tksea.top/portal/upgrade/pro" {
|
||||
t.Fatalf("GetPortalLogicalGroup() packaging = %+v", group)
|
||||
}
|
||||
|
||||
models, err := actions.ListPortalLogicalGroupModels(ctx, "gpt-shared")
|
||||
if err != nil {
|
||||
@@ -192,4 +206,7 @@ func TestNewActionSetPortalLogicalGroups(t *testing.T) {
|
||||
if payload["logical_group"].Recommendation != "优先使用 gpt-5.4" {
|
||||
t.Fatalf("portal logical group recommendation = %+v, want configured recommendation", payload["logical_group"])
|
||||
}
|
||||
if payload["logical_group"].PackageTier != "pro" {
|
||||
t.Fatalf("portal logical group package_tier = %+v, want configured package tier", payload["logical_group"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE logical_groups ADD COLUMN visibility_scope TEXT NOT NULL DEFAULT 'public';
|
||||
ALTER TABLE logical_groups ADD COLUMN package_tier TEXT NOT NULL DEFAULT 'standard';
|
||||
ALTER TABLE logical_groups ADD COLUMN purchase_cta_label TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE logical_groups ADD COLUMN purchase_cta_url TEXT NOT NULL DEFAULT '';
|
||||
@@ -13,6 +13,8 @@ const (
|
||||
defaultUserModelTTLSeconds = 1800
|
||||
defaultFailoverThreshold = 2
|
||||
defaultCooldownSeconds = 600
|
||||
defaultLogicalGroupVisibility = "public"
|
||||
defaultLogicalGroupPackageTier = "standard"
|
||||
)
|
||||
|
||||
type LogicalGroup struct {
|
||||
@@ -24,6 +26,10 @@ type LogicalGroup struct {
|
||||
UsageScenario string
|
||||
Recommendation string
|
||||
NextStepHint string
|
||||
VisibilityScope string
|
||||
PackageTier string
|
||||
PurchaseCTALabel string
|
||||
PurchaseCTAURL string
|
||||
RoutePolicy string
|
||||
StickyMode string
|
||||
ConversationTTLSeconds int
|
||||
@@ -58,13 +64,17 @@ func (r *LogicalGroupsRepo) Create(ctx context.Context, group LogicalGroup) (int
|
||||
usage_scenario,
|
||||
recommendation,
|
||||
next_step_hint,
|
||||
visibility_scope,
|
||||
package_tier,
|
||||
purchase_cta_label,
|
||||
purchase_cta_url,
|
||||
route_policy,
|
||||
sticky_mode,
|
||||
conversation_ttl_seconds,
|
||||
user_model_ttl_seconds,
|
||||
failover_threshold,
|
||||
cooldown_seconds
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
group.LogicalGroupID,
|
||||
group.DisplayName,
|
||||
group.Status,
|
||||
@@ -72,6 +82,10 @@ func (r *LogicalGroupsRepo) Create(ctx context.Context, group LogicalGroup) (int
|
||||
group.UsageScenario,
|
||||
group.Recommendation,
|
||||
group.NextStepHint,
|
||||
group.VisibilityScope,
|
||||
group.PackageTier,
|
||||
group.PurchaseCTALabel,
|
||||
group.PurchaseCTAURL,
|
||||
group.RoutePolicy,
|
||||
group.StickyMode,
|
||||
group.ConversationTTLSeconds,
|
||||
@@ -99,7 +113,7 @@ func (r *LogicalGroupsRepo) GetByLogicalGroupID(ctx context.Context, logicalGrou
|
||||
var group LogicalGroup
|
||||
if err := r.db.QueryRowContext(
|
||||
ctx,
|
||||
`SELECT id, logical_group_id, display_name, status, description, usage_scenario, recommendation, next_step_hint, route_policy, sticky_mode, conversation_ttl_seconds, user_model_ttl_seconds, failover_threshold, cooldown_seconds, created_at, updated_at
|
||||
`SELECT id, logical_group_id, display_name, status, description, usage_scenario, recommendation, next_step_hint, visibility_scope, package_tier, purchase_cta_label, purchase_cta_url, route_policy, sticky_mode, conversation_ttl_seconds, user_model_ttl_seconds, failover_threshold, cooldown_seconds, created_at, updated_at
|
||||
FROM logical_groups
|
||||
WHERE logical_group_id = ?`,
|
||||
logicalGroupID,
|
||||
@@ -112,6 +126,10 @@ func (r *LogicalGroupsRepo) GetByLogicalGroupID(ctx context.Context, logicalGrou
|
||||
&group.UsageScenario,
|
||||
&group.Recommendation,
|
||||
&group.NextStepHint,
|
||||
&group.VisibilityScope,
|
||||
&group.PackageTier,
|
||||
&group.PurchaseCTALabel,
|
||||
&group.PurchaseCTAURL,
|
||||
&group.RoutePolicy,
|
||||
&group.StickyMode,
|
||||
&group.ConversationTTLSeconds,
|
||||
@@ -129,7 +147,7 @@ func (r *LogicalGroupsRepo) GetByLogicalGroupID(ctx context.Context, logicalGrou
|
||||
func (r *LogicalGroupsRepo) List(ctx context.Context) ([]LogicalGroup, error) {
|
||||
rows, err := r.db.QueryContext(
|
||||
ctx,
|
||||
`SELECT id, logical_group_id, display_name, status, description, usage_scenario, recommendation, next_step_hint, route_policy, sticky_mode, conversation_ttl_seconds, user_model_ttl_seconds, failover_threshold, cooldown_seconds, created_at, updated_at
|
||||
`SELECT id, logical_group_id, display_name, status, description, usage_scenario, recommendation, next_step_hint, visibility_scope, package_tier, purchase_cta_label, purchase_cta_url, route_policy, sticky_mode, conversation_ttl_seconds, user_model_ttl_seconds, failover_threshold, cooldown_seconds, created_at, updated_at
|
||||
FROM logical_groups
|
||||
ORDER BY id ASC`,
|
||||
)
|
||||
@@ -150,6 +168,10 @@ func (r *LogicalGroupsRepo) List(ctx context.Context) ([]LogicalGroup, error) {
|
||||
&group.UsageScenario,
|
||||
&group.Recommendation,
|
||||
&group.NextStepHint,
|
||||
&group.VisibilityScope,
|
||||
&group.PackageTier,
|
||||
&group.PurchaseCTALabel,
|
||||
&group.PurchaseCTAURL,
|
||||
&group.RoutePolicy,
|
||||
&group.StickyMode,
|
||||
&group.ConversationTTLSeconds,
|
||||
@@ -178,7 +200,7 @@ func (r *LogicalGroupsRepo) UpdateByLogicalGroupID(ctx context.Context, group Lo
|
||||
result, err := r.db.ExecContext(
|
||||
ctx,
|
||||
`UPDATE logical_groups
|
||||
SET display_name = ?, status = ?, description = ?, usage_scenario = ?, recommendation = ?, next_step_hint = ?, route_policy = ?, sticky_mode = ?, conversation_ttl_seconds = ?, user_model_ttl_seconds = ?, failover_threshold = ?, cooldown_seconds = ?, updated_at = CURRENT_TIMESTAMP
|
||||
SET display_name = ?, status = ?, description = ?, usage_scenario = ?, recommendation = ?, next_step_hint = ?, visibility_scope = ?, package_tier = ?, purchase_cta_label = ?, purchase_cta_url = ?, route_policy = ?, sticky_mode = ?, conversation_ttl_seconds = ?, user_model_ttl_seconds = ?, failover_threshold = ?, cooldown_seconds = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE logical_group_id = ?`,
|
||||
group.DisplayName,
|
||||
group.Status,
|
||||
@@ -186,6 +208,10 @@ func (r *LogicalGroupsRepo) UpdateByLogicalGroupID(ctx context.Context, group Lo
|
||||
group.UsageScenario,
|
||||
group.Recommendation,
|
||||
group.NextStepHint,
|
||||
group.VisibilityScope,
|
||||
group.PackageTier,
|
||||
group.PurchaseCTALabel,
|
||||
group.PurchaseCTAURL,
|
||||
group.RoutePolicy,
|
||||
group.StickyMode,
|
||||
group.ConversationTTLSeconds,
|
||||
@@ -235,6 +261,10 @@ func normalizeLogicalGroup(group LogicalGroup) (LogicalGroup, error) {
|
||||
group.UsageScenario = strings.TrimSpace(group.UsageScenario)
|
||||
group.Recommendation = strings.TrimSpace(group.Recommendation)
|
||||
group.NextStepHint = strings.TrimSpace(group.NextStepHint)
|
||||
group.VisibilityScope = strings.TrimSpace(group.VisibilityScope)
|
||||
group.PackageTier = strings.TrimSpace(group.PackageTier)
|
||||
group.PurchaseCTALabel = strings.TrimSpace(group.PurchaseCTALabel)
|
||||
group.PurchaseCTAURL = strings.TrimSpace(group.PurchaseCTAURL)
|
||||
group.RoutePolicy = strings.TrimSpace(group.RoutePolicy)
|
||||
group.StickyMode = strings.TrimSpace(group.StickyMode)
|
||||
|
||||
@@ -250,6 +280,12 @@ func normalizeLogicalGroup(group LogicalGroup) (LogicalGroup, error) {
|
||||
if group.RoutePolicy == "" {
|
||||
group.RoutePolicy = defaultLogicalGroupRoutePolicy
|
||||
}
|
||||
if group.VisibilityScope == "" {
|
||||
group.VisibilityScope = defaultLogicalGroupVisibility
|
||||
}
|
||||
if group.PackageTier == "" {
|
||||
group.PackageTier = defaultLogicalGroupPackageTier
|
||||
}
|
||||
if group.StickyMode == "" {
|
||||
group.StickyMode = defaultLogicalGroupStickyMode
|
||||
}
|
||||
|
||||
@@ -12,13 +12,17 @@ func TestLogicalGroupsRepoCreateGetUpdateDelete(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
id, err := store.LogicalGroups().Create(ctx, LogicalGroup{
|
||||
LogicalGroupID: "gpt-shared",
|
||||
DisplayName: "GPT Shared",
|
||||
Status: "active",
|
||||
Description: "shared group",
|
||||
UsageScenario: "适合统一 GPT 产品入口。",
|
||||
Recommendation: "优先使用 gpt-5.4。",
|
||||
NextStepHint: "先创建测试 Key。",
|
||||
LogicalGroupID: "gpt-shared",
|
||||
DisplayName: "GPT Shared",
|
||||
Status: "active",
|
||||
Description: "shared group",
|
||||
UsageScenario: "适合统一 GPT 产品入口。",
|
||||
Recommendation: "优先使用 gpt-5.4。",
|
||||
NextStepHint: "先创建测试 Key。",
|
||||
VisibilityScope: "login_required",
|
||||
PackageTier: "pro",
|
||||
PurchaseCTALabel: "升级到 Pro",
|
||||
PurchaseCTAURL: "https://sub.tksea.top/portal/upgrade/pro",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Create() error = %v", err)
|
||||
@@ -40,6 +44,9 @@ func TestLogicalGroupsRepoCreateGetUpdateDelete(t *testing.T) {
|
||||
if group.UsageScenario != "适合统一 GPT 产品入口。" || group.Recommendation != "优先使用 gpt-5.4。" || group.NextStepHint != "先创建测试 Key。" {
|
||||
t.Fatalf("guidance fields = %+v, want persisted guidance", group)
|
||||
}
|
||||
if group.VisibilityScope != "login_required" || group.PackageTier != "pro" || group.PurchaseCTALabel != "升级到 Pro" || group.PurchaseCTAURL != "https://sub.tksea.top/portal/upgrade/pro" {
|
||||
t.Fatalf("packaging fields = %+v, want persisted packaging", group)
|
||||
}
|
||||
|
||||
if err := store.LogicalGroups().UpdateByLogicalGroupID(ctx, LogicalGroup{
|
||||
LogicalGroupID: "gpt-shared",
|
||||
@@ -49,6 +56,10 @@ func TestLogicalGroupsRepoCreateGetUpdateDelete(t *testing.T) {
|
||||
UsageScenario: "适合更新后的产品入口。",
|
||||
Recommendation: "优先做连通性验证。",
|
||||
NextStepHint: "先确认订阅再调用。",
|
||||
VisibilityScope: "entitled_only",
|
||||
PackageTier: "enterprise",
|
||||
PurchaseCTALabel: "联系销售升级",
|
||||
PurchaseCTAURL: "https://sub.tksea.top/portal/contact-sales",
|
||||
RoutePolicy: "priority",
|
||||
StickyMode: "user_preferred",
|
||||
ConversationTTLSeconds: 3600,
|
||||
@@ -69,6 +80,9 @@ func TestLogicalGroupsRepoCreateGetUpdateDelete(t *testing.T) {
|
||||
if updated.UsageScenario != "适合更新后的产品入口。" || updated.Recommendation != "优先做连通性验证。" || updated.NextStepHint != "先确认订阅再调用。" {
|
||||
t.Fatalf("updated guidance = %+v, want updated guidance fields", updated)
|
||||
}
|
||||
if updated.VisibilityScope != "entitled_only" || updated.PackageTier != "enterprise" || updated.PurchaseCTALabel != "联系销售升级" || updated.PurchaseCTAURL != "https://sub.tksea.top/portal/contact-sales" {
|
||||
t.Fatalf("updated packaging = %+v, want updated packaging fields", updated)
|
||||
}
|
||||
|
||||
if err := store.LogicalGroups().DeleteByLogicalGroupID(ctx, "gpt-shared"); err != nil {
|
||||
t.Fatalf("DeleteByLogicalGroupID() error = %v", err)
|
||||
|
||||
@@ -73,6 +73,12 @@ assert_contains_file "$HTML_FILE" "路由策略"
|
||||
assert_contains_file "$HTML_FILE" "usage_scenario"
|
||||
assert_contains_file "$HTML_FILE" "recommendation"
|
||||
assert_contains_file "$HTML_FILE" "next_step_hint"
|
||||
assert_contains_file "$HTML_FILE" "visibility_scope"
|
||||
assert_contains_file "$HTML_FILE" "package_tier"
|
||||
assert_contains_file "$HTML_FILE" "purchase_cta_label"
|
||||
assert_contains_file "$HTML_FILE" "purchase_cta_url"
|
||||
assert_contains_file "$HTML_FILE" "logicalGroupVisibleForViewer"
|
||||
assert_contains_file "$HTML_FILE" "cta-link"
|
||||
assert_contains_file "$HTML_FILE" "已开通订阅"
|
||||
assert_contains_file "$HTML_FILE" "已授予权限"
|
||||
assert_contains_file "$HTML_FILE" "归属待整理"
|
||||
@@ -128,6 +134,10 @@ assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "shadow_host_id"
|
||||
assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "usage_scenario"
|
||||
assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "recommendation"
|
||||
assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "next_step_hint"
|
||||
assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "visibility_scope"
|
||||
assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "package_tier"
|
||||
assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "purchase_cta_label"
|
||||
assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "purchase_cta_url"
|
||||
assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "首版页面只覆盖新增与查看"
|
||||
assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" 'credentials: "include"'
|
||||
assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "/portal-admin-api"
|
||||
|
||||
Reference in New Issue
Block a user