Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b57cecb62d | ||
|
|
8dc52d052c | ||
|
|
6147021ae8 | ||
|
|
ae31d371de | ||
|
|
c20a53cd8a | ||
|
|
1bb56364c2 | ||
|
|
758b0f875e |
@@ -544,7 +544,8 @@ jobs:
|
||||
- app-schema
|
||||
- app-a-b
|
||||
- app-c-a-l
|
||||
- app-c-m-r
|
||||
- app-c-m-o
|
||||
- app-c-p-r
|
||||
- app-c-s-z
|
||||
- app-c-other
|
||||
- app-d-r
|
||||
@@ -692,7 +693,8 @@ jobs:
|
||||
- app-schema
|
||||
- app-a-b
|
||||
- app-c-a-l
|
||||
- app-c-m-r
|
||||
- app-c-m-o
|
||||
- app-c-p-r
|
||||
- app-c-s-z
|
||||
- app-c-other
|
||||
- app-d-r
|
||||
@@ -1597,7 +1599,10 @@ jobs:
|
||||
name: Policy
|
||||
needs: lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
# Full policy regenerates and validates the runtime Schema several times.
|
||||
# Keep job-level headroom for large reviewed command-surface additions;
|
||||
# individual policy gates retain their own fail-closed checks.
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -153,7 +153,7 @@ var paramAliasCompleteCommands = map[string][]string{
|
||||
"devdoc article search": {"devdoc", "article", "search", "--query", "fixture", "--page", "2", "--size", "7"},
|
||||
"ding +receiver-status": {"ding", "+receiver-status", "--ding-id", "ding-1"},
|
||||
"ding message receiver-status": {"ding", "message", "receiver-status", "--ding-id", "ding-1"},
|
||||
"ding message send": {"ding", "message", "send", "--robot-code", "robot-1", "--content", "fixture", "--users", "user-1", "--yes"},
|
||||
"ding message send": {"ding", "message", "send", "--robot-code", "robot-1", "--content", "fixture", "--users", "user-1"},
|
||||
"doc +comment-create": {"doc", "+comment-create", "--node", "node-1", "--content", "fixture comment", "--yes"},
|
||||
"doc +comment-list": {"doc", "+comment-list", "--node", "node-1", "--limit", "7", "--cursor", "cursor-1"},
|
||||
"doc +comment-reply": {"doc", "+comment-reply", "--node", "node-1", "--comment-key", "comment-1", "--content", "fixture reply", "--yes"},
|
||||
@@ -221,8 +221,8 @@ var paramAliasCompleteCommands = map[string][]string{
|
||||
"drive search": {"drive", "search", "--query", "fixture", "--created-from", "1", "--created-to", "2", "--modified-from", "3", "--modified-to", "4", "--creator-uids", "user-1,user-2"},
|
||||
"drive upload": {"drive", "upload", "--file", "../../go.mod", "--space-id", "space-1"},
|
||||
"drive upload-info": {"drive", "upload-info", "--file-name", "fixture.txt", "--file-size", "7", "--space-id", "space-1"},
|
||||
"mail +find-mail-user": {"mail", "+find-mail-user", "--query", "fixture", "--limit", "7"},
|
||||
"mail folder update": {"mail", "folder", "update", "--email", "fixture@example.com", "--id", "folder-1", "--name", "Fixture Folder", "--yes"},
|
||||
"mail +find-mail-user": {"mail", "+find-mail-user", "--query", "fixture", "--limit", "7", "--cursor", "cursor-1"},
|
||||
"mail folder update": {"mail", "folder", "update", "--email", "fixture@example.com", "--id", "folder-1", "--name", "Fixture Folder"},
|
||||
"mail message search": {"mail", "message", "search", "--email", "fixture@example.com", "--query", "subject:fixture"},
|
||||
"mail thread list": {"mail", "thread", "list", "--email", "fixture@example.com", "--folder", "folder-1", "--limit", "7"},
|
||||
"mail user search": {"mail", "user", "search", "--keyword", "fixture"},
|
||||
@@ -231,12 +231,59 @@ var paramAliasCompleteCommands = map[string][]string{
|
||||
"report list": {"report", "list", "--start", "2026-03-10T00:00:00+08:00", "--end", "2026-03-10T23:59:59+08:00"},
|
||||
}
|
||||
|
||||
// paramAliasCandidateCompleteCommands contains complete invocations for the
|
||||
// reviewed Minutes/TODO/Wiki joint draft. Keeping candidate-only commands in a
|
||||
// separate map lets this test file land before the draft replaces the formal
|
||||
// paramAliasCandidateCompleteCommands contains complete invocations for
|
||||
// reviewed parameter-concept product drafts. Keeping candidate-only commands
|
||||
// in a separate map lets a test change land before a draft replaces the formal
|
||||
// param_concepts.json: inactive candidate templates are ignored, while every
|
||||
// command becomes mandatory as soon as one of its reviewed aliases is active.
|
||||
var paramAliasCandidateCompleteCommands = map[string][]string{
|
||||
"attendance +check-record": {"attendance", "+check-record", "--users", "user-1,user-2", "--start", "2026-03-10 00:00:00", "--end", "2026-03-10 23:59:59"},
|
||||
"attendance +get-adjustment-rule": {"attendance", "+get-adjustment-rule", "--adjustment-id", "adjustment-1"},
|
||||
"attendance +get-approve-template": {"attendance", "+get-approve-template", "--type", "leave"},
|
||||
"attendance +get-checkin-record": {"attendance", "+get-checkin-record", "--operator-corp-id", "corp-1", "--operator-staff-id", "staff-operator", "--staff-ids", "staff-1,staff-2", "--start", "2026-03-10 00:00:00", "--end", "2026-03-10 23:59:59"},
|
||||
"attendance +get-leave-records": {"attendance", "+get-leave-records", "--user", "user-1", "--start", "2026-03-01", "--end", "2026-03-31", "--leave-code", "annual_leave"},
|
||||
"attendance +get-overtime-rule": {"attendance", "+get-overtime-rule", "--overtime-id", "overtime-1"},
|
||||
"attendance +get-schedule": {"attendance", "+get-schedule", "--users", "user-1,user-2", "--start", "2026-03-10", "--end", "2026-03-11"},
|
||||
"attendance +get-self-setting": {"attendance", "+get-self-setting", "--user", "user-1", "--setting-scene", "checkRemind"},
|
||||
"attendance +get-summary": {"attendance", "+get-summary", "--user", "user-1", "--date", "2026-03-10", "--stats-type", "week"},
|
||||
"attendance +list-approve": {"attendance", "+list-approve", "--users", "user-1,user-2", "--types", "leave", "--start", "2026-03-01", "--end", "2026-03-31"},
|
||||
"attendance +query-report-data": {"attendance", "+query-report-data", "--users", "user-1,user-2", "--columns", "attendance_days,late_count", "--start", "2026-03-01", "--end", "2026-03-31"},
|
||||
"attendance +search-adjustment-rule": {"attendance", "+search-adjustment-rule", "--query", "fixture", "--page", "2", "--limit", "7"},
|
||||
"attendance +search-class": {"attendance", "+search-class", "--filter-type", "name", "--query", "fixture"},
|
||||
"attendance +search-group": {"attendance", "+search-group", "--type", "FIXED"},
|
||||
"attendance +search-overtime-rule": {"attendance", "+search-overtime-rule", "--query", "fixture", "--page", "2", "--limit", "7"},
|
||||
"ding +list": {"ding", "+list", "--cursor", "0", "--type", "ALL"},
|
||||
"ding +recall-personal": {"ding", "+recall-personal", "--id", "ding-1", "--yes"},
|
||||
"ding +send-personal": {"ding", "+send-personal", "--users", appFixtureCurrentDOpenID, "--content", "fixture", "--yes"},
|
||||
"mail +contact-list": {"mail", "+contact-list", "--email", "fixture@example.com", "--limit", "7", "--cursor", "cursor-1"},
|
||||
"mail +folder-list": {"mail", "+folder-list", "--email", "fixture@example.com", "--folder", "folder-1"},
|
||||
"mail +message": {"mail", "+message", "--email", "fixture@example.com", "--id", "message-1"},
|
||||
"mail +messages": {"mail", "+messages", "--email", "fixture@example.com", "--ids", "message-1,message-2"},
|
||||
"mail +recent-mail": {"mail", "+recent-mail", "--limit", "7", "--cursor", "cursor-1"},
|
||||
"mail +search-mail": {"mail", "+search-mail", "--query", "fixture", "--size", "7", "--cursor", "cursor-1"},
|
||||
"mail +template-list": {"mail", "+template-list", "--email", "fixture@example.com", "--limit", "7", "--cursor", "cursor-1"},
|
||||
"mail +thread": {"mail", "+thread", "--email", "fixture@example.com", "--id", "thread-1"},
|
||||
"mail +thread-list": {"mail", "+thread-list", "--email", "fixture@example.com", "--folder", "folder-1", "--cursor", "cursor-1"},
|
||||
"mail +triage": {"mail", "+triage", "--query", "fixture", "--limit", "7", "--cursor", "cursor-1"},
|
||||
"mail +unread-mail": {"mail", "+unread-mail", "--size", "7", "--cursor", "cursor-1"},
|
||||
"mail +user-search": {"mail", "+user-search", "--keyword", "fixture", "--cursor", "cursor-1"},
|
||||
"markdown create": {"markdown", "create", "--content", "# Fixture", "--name", "fixture.md", "--space-id", "space-1"},
|
||||
"markdown diff": {"markdown", "diff", "--node", "node-1", "--version", "1", "--version2", "2", "--context", "3"},
|
||||
"markdown fetch": {"markdown", "fetch", "--node", "node-1", "--space-id", "space-1", "--output", "/tmp/dws-markdown-fixture.md"},
|
||||
"markdown overwrite": {"markdown", "overwrite", "--node", "node-1", "--content", "# Fixture", "--name", "fixture.md", "--space-id", "space-1", "--yes"},
|
||||
"markdown patch": {"markdown", "patch", "--node", "node-1", "--pattern", "old", "--content", "new", "--regex", "--space-id", "space-1", "--yes"},
|
||||
"oa +list-cc": {"oa", "+list-cc", "--page", "2"},
|
||||
"oa +list-executed": {"oa", "+list-executed", "--limit", "7", "--page", "2"},
|
||||
"oa +list-forms": {"oa", "+list-forms", "--cursor", "2"},
|
||||
"oa +list-pending": {"oa", "+list-pending", "--start", "1773072000000", "--end", "1773158399000", "--page", "2"},
|
||||
"oa +list-submitted": {"oa", "+list-submitted", "--page", "2"},
|
||||
"oa +my-initiated": {"oa", "+my-initiated", "--page", "2"},
|
||||
"report +outbox-list": {"report", "+outbox-list", "--size", "7"},
|
||||
"report +report-latest": {"report", "+report-latest", "--keyword", "Fixture", "--start", "2026-03-01T00:00:00+08:00", "--end", "2026-03-10T00:00:00+08:00"},
|
||||
"report +template-search": {"report", "+template-search", "--query", "fixture"},
|
||||
"sheet +list-sheets": {"sheet", "+list-sheets", "--node", "node-1"},
|
||||
"sheet +read": {"sheet", "+read", "--node", "node-1", "--sheet-id", "Sheet1"},
|
||||
|
||||
"minutes +detail": {"minutes", "+detail", "--ids", "u1,u2"},
|
||||
"minutes +latest": {"minutes", "+latest", "--keyword", "fixture"},
|
||||
"minutes +list-all": {"minutes", "+list-all", "--limit", "7"},
|
||||
@@ -296,6 +343,20 @@ var paramAliasCandidateCompleteCommands = map[string][]string{
|
||||
// that case the shared command template above cannot contain every canonical
|
||||
// flag at once, so select a fixture-specific complete invocation here.
|
||||
var paramAliasCompleteCommandVariants = map[string]map[string][]string{
|
||||
"markdown create": {
|
||||
"file": {"markdown", "create", "--file", "../../README.md", "--name", "fixture.md", "--space-id", "space-1"},
|
||||
},
|
||||
"markdown diff": {
|
||||
"file": {"markdown", "diff", "--node", "node-1", "--file", "../../README.md", "--context", "3"},
|
||||
},
|
||||
"markdown overwrite": {
|
||||
"file": {"markdown", "overwrite", "--node", "node-1", "--file", "../../README.md", "--name", "fixture.md", "--space-id", "space-1", "--yes"},
|
||||
"dry-run": {"markdown", "overwrite", "--node", "node-1", "--content", "# Fixture", "--name", "fixture.md", "--space-id", "space-1", "--dry-run"},
|
||||
},
|
||||
"markdown patch": {
|
||||
"dry-run": {"markdown", "patch", "--node", "node-1", "--pattern", "old", "--content", "new", "--regex", "--dry-run"},
|
||||
},
|
||||
|
||||
"doc +copy": {
|
||||
"folder": {"doc", "+copy", "--node", "node-1", "--folder", "folder-1", "--yes"},
|
||||
"workspace": {"doc", "+copy", "--node", "node-1", "--workspace", "workspace-1", "--yes"},
|
||||
@@ -969,6 +1030,121 @@ func assertParamAliasCannotBypassConfirmation(t *testing.T, aliasArgs []string)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCrossPlatformCoverageEightProductTemplatedParamAliasesCannotBypassConfirmation
|
||||
// exercises every distinct reviewed mutating complete-command template in the
|
||||
// eight-product Shortcut expansion. The fixture gate already proves every
|
||||
// alias resolves through PreParse; this gate removes the confirmation flag
|
||||
// from one active alias invocation per distinct template and requires the
|
||||
// runtime boundary to stop it before the first transport call. An explicit
|
||||
// --dry-run is a reviewed preview path and must not carry a bypass flag.
|
||||
func TestCrossPlatformCoverageEightProductTemplatedParamAliasesCannotBypassConfirmation(t *testing.T) {
|
||||
concepts, err := cli.LoadParamConcepts()
|
||||
if err != nil {
|
||||
t.Fatalf("LoadParamConcepts() error = %v", err)
|
||||
}
|
||||
|
||||
requiredTemplates := make(map[string]bool)
|
||||
coveredTemplates := make(map[string]bool)
|
||||
for _, fixture := range concepts.Fixture {
|
||||
if strings.HasPrefix(fixture.Expect, "did-you-mean:") {
|
||||
continue
|
||||
}
|
||||
product, _, _ := strings.Cut(fixture.Command, " ")
|
||||
switch product {
|
||||
case "attendance", "mail", "oa", "ding", "report", "sheet", "whiteboard", "markdown":
|
||||
default:
|
||||
continue
|
||||
}
|
||||
complete, ok := paramAliasCompleteCommand(fixture.Command, fixture.Expect)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
_, yesCount := removeExactArg(complete, "--yes")
|
||||
_, userSayYesCount := removeExactArg(complete, "--user-say-yes")
|
||||
confirmationCount := yesCount + userSayYesCount
|
||||
confirmationArg := "--yes"
|
||||
if userSayYesCount == 1 {
|
||||
confirmationArg = "--user-say-yes"
|
||||
}
|
||||
_, dryRunCount := removeExactArg(complete, "--dry-run")
|
||||
if dryRunCount > 1 {
|
||||
t.Errorf("template must contain --dry-run at most once: command=%q args=%v", fixture.Command, complete)
|
||||
continue
|
||||
}
|
||||
if meta, exists := cli.ResolveMeta(fixture.Command); exists {
|
||||
switch meta.Safety.Confirmation {
|
||||
case "user_required":
|
||||
if dryRunCount == 1 {
|
||||
if confirmationCount != 0 {
|
||||
t.Errorf("Schema-confirmed dry-run template must not contain a confirmation bypass flag: command=%q args=%v", fixture.Command, complete)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if confirmationCount != 1 {
|
||||
t.Errorf("Schema-confirmed template must contain exactly one reviewed confirmation flag: command=%q confirmation=%q args=%v", fixture.Command, meta.Safety.Confirmation, complete)
|
||||
continue
|
||||
}
|
||||
case "not_required":
|
||||
if confirmationCount != 0 {
|
||||
t.Errorf("Schema-unconfirmed template must not contain a confirmation bypass flag: command=%q confirmation=%q args=%v", fixture.Command, meta.Safety.Confirmation, complete)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if confirmationCount == 0 {
|
||||
continue
|
||||
}
|
||||
if confirmationCount != 1 {
|
||||
t.Errorf("confirmation template must contain exactly one reviewed confirmation flag: command=%q args=%v", fixture.Command, complete)
|
||||
continue
|
||||
}
|
||||
|
||||
templateKey := fixture.Command + "\x00" + strings.Join(complete, "\x00")
|
||||
requiredTemplates[templateKey] = true
|
||||
if coveredTemplates[templateKey] {
|
||||
continue
|
||||
}
|
||||
aliasArgs, replacements := replaceLongFlag(complete, fixture.Expect, fixture.Emitted)
|
||||
if replacements != 1 {
|
||||
t.Errorf("confirmation template for %q/%q must contain canonical --%s exactly once; replacements=%d args=%v", fixture.Command, fixture.Emitted, fixture.Expect, replacements, complete)
|
||||
continue
|
||||
}
|
||||
coveredTemplates[templateKey] = true
|
||||
t.Run(fixture.Command+"/"+fixture.Emitted, func(t *testing.T) {
|
||||
assertTemplatedParamAliasCannotBypassConfirmation(t, fixture.Command, confirmationArg, aliasArgs)
|
||||
})
|
||||
}
|
||||
|
||||
if len(requiredTemplates) == 0 {
|
||||
t.Fatal("reviewed complete-command templates contain no confirmation cases")
|
||||
}
|
||||
if len(coveredTemplates) != len(requiredTemplates) {
|
||||
t.Fatalf("templated confirmation coverage = %d, want %d", len(coveredTemplates), len(requiredTemplates))
|
||||
}
|
||||
}
|
||||
|
||||
func assertTemplatedParamAliasCannotBypassConfirmation(t *testing.T, command, confirmationArg string, aliasArgs []string) {
|
||||
t.Helper()
|
||||
unconfirmedArgs, removals := removeExactArg(aliasArgs, confirmationArg)
|
||||
if removals != 1 {
|
||||
t.Fatalf("confirmation template must contain %s exactly once; removals=%d args=%v", confirmationArg, removals, aliasArgs)
|
||||
}
|
||||
|
||||
caller := ¶mAliasCaptureCaller{}
|
||||
ctx, err := executeParamAliasPayloadE2E(t, caller, unconfirmedArgs...)
|
||||
if ctx == nil {
|
||||
t.Fatal("unconfirmed alias command skipped PreParse")
|
||||
}
|
||||
var appErr *apperrors.Error
|
||||
if errors.As(err, &appErr) && appErr.Reason == "confirmation_required" {
|
||||
if len(caller.calls) != 0 {
|
||||
t.Fatalf("unconfirmed alias crossed the transport boundary before confirmation: args=%v calls=%#v", unconfirmedArgs, caller.calls)
|
||||
}
|
||||
return
|
||||
}
|
||||
t.Fatalf("unconfirmed alias command error = %#v, want confirmation_required\ncommand=%q args=%v calls=%#v", err, command, unconfirmedArgs, caller.calls)
|
||||
}
|
||||
|
||||
func assertParamAliasFinalPayloadEquivalent(t *testing.T, command string, canonicalArgs, aliasArgs []string) {
|
||||
t.Helper()
|
||||
canonicalCaller := ¶mAliasCaptureCaller{}
|
||||
|
||||
@@ -1759,12 +1759,326 @@ var generatedParamAliases = []ParamAliasEntry{
|
||||
},
|
||||
Blocked: []string{"id", "task-id"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance +check-record",
|
||||
Aliases: map[string]string{
|
||||
"begin": "start",
|
||||
"end-date": "end",
|
||||
"end-time": "end",
|
||||
"from": "start",
|
||||
"from-date": "start",
|
||||
"max-time": "end",
|
||||
"min-time": "start",
|
||||
"since": "start",
|
||||
"staff-ids": "users",
|
||||
"start-date": "start",
|
||||
"start-time": "start",
|
||||
"target-user-ids": "users",
|
||||
"time-max": "end",
|
||||
"time-min": "start",
|
||||
"to": "end",
|
||||
"until": "end",
|
||||
"user-id-list": "users",
|
||||
"user-ids": "users",
|
||||
},
|
||||
Blocked: []string{"at-user-ids", "date", "staff-id", "time", "uid", "user", "user-id", "userid"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance +check-result",
|
||||
Aliases: map[string]string{
|
||||
"user-ids": "users",
|
||||
"begin": "start",
|
||||
"end-date": "end",
|
||||
"end-time": "end",
|
||||
"from": "start",
|
||||
"from-date": "start",
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"max-time": "end",
|
||||
"min-time": "start",
|
||||
"page-size": "limit",
|
||||
"per-page": "limit",
|
||||
"since": "start",
|
||||
"size": "limit",
|
||||
"staff-ids": "users",
|
||||
"start-date": "start",
|
||||
"start-time": "start",
|
||||
"take": "limit",
|
||||
"target-user-ids": "users",
|
||||
"time-max": "end",
|
||||
"time-min": "start",
|
||||
"to": "end",
|
||||
"top": "limit",
|
||||
"until": "end",
|
||||
"user-id-list": "users",
|
||||
"user-ids": "users",
|
||||
},
|
||||
Blocked: []string{"at-user-ids", "staff-id", "uid", "user", "user-id", "userid"},
|
||||
Blocked: []string{"at-user-ids", "count", "cursor", "date", "page", "page-token", "staff-id", "time", "uid", "user", "user-id", "userid"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance +get-adjustment-rule",
|
||||
Aliases: map[string]string{
|
||||
"adjustment-rule-id": "adjustment-id",
|
||||
"rule-id": "adjustment-id",
|
||||
},
|
||||
Blocked: []string{"class-id", "group-id", "id", "overtime-id", "plan-id", "result-id"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance +get-approve-template",
|
||||
Aliases: map[string]string{
|
||||
"approval-type": "type",
|
||||
"biz-type": "type",
|
||||
},
|
||||
Blocked: []string{"approval-types", "biz-types", "types"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance +get-checkin-record",
|
||||
Aliases: map[string]string{
|
||||
"begin": "start",
|
||||
"corp-id": "operator-corp-id",
|
||||
"end-date": "end",
|
||||
"end-time": "end",
|
||||
"from": "start",
|
||||
"from-date": "start",
|
||||
"max-time": "end",
|
||||
"min-time": "start",
|
||||
"operator-company-id": "operator-corp-id",
|
||||
"operator-id": "operator-staff-id",
|
||||
"operator-user-id": "operator-staff-id",
|
||||
"since": "start",
|
||||
"start-date": "start",
|
||||
"start-time": "start",
|
||||
"target-user-ids": "staff-ids",
|
||||
"time-max": "end",
|
||||
"time-min": "start",
|
||||
"to": "end",
|
||||
"until": "end",
|
||||
"user-id-list": "staff-ids",
|
||||
"user-ids": "staff-ids",
|
||||
"users": "staff-ids",
|
||||
},
|
||||
Blocked: []string{"at-user-ids", "date", "name", "time", "to-user"},
|
||||
Ambiguous: []string{"staff-id", "uid", "user", "user-id", "userid"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance +get-leave-records",
|
||||
Aliases: map[string]string{
|
||||
"begin": "start",
|
||||
"end-date": "end",
|
||||
"end-time": "end",
|
||||
"from": "start",
|
||||
"from-date": "start",
|
||||
"max-time": "end",
|
||||
"min-time": "start",
|
||||
"since": "start",
|
||||
"staff-id": "user",
|
||||
"start-date": "start",
|
||||
"start-time": "start",
|
||||
"time-max": "end",
|
||||
"time-min": "start",
|
||||
"to": "end",
|
||||
"uid": "user",
|
||||
"until": "end",
|
||||
"user-id": "user",
|
||||
"userid": "user",
|
||||
"vacation-type-code": "leave-code",
|
||||
},
|
||||
Blocked: []string{"at-user-ids", "date", "id", "leave-name", "leave-names", "name", "rule-id", "time", "to-user", "user-ids", "users", "vacation-name"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance +get-overtime-rule",
|
||||
Aliases: map[string]string{
|
||||
"overtime-rule-id": "overtime-id",
|
||||
"rule-id": "overtime-id",
|
||||
},
|
||||
Blocked: []string{"adjustment-id", "class-id", "group-id", "id", "plan-id", "result-id"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance +get-schedule",
|
||||
Aliases: map[string]string{
|
||||
"begin": "start",
|
||||
"end-date": "end",
|
||||
"end-time": "end",
|
||||
"from": "start",
|
||||
"from-date": "start",
|
||||
"max-time": "end",
|
||||
"min-time": "start",
|
||||
"since": "start",
|
||||
"staff-ids": "users",
|
||||
"start-date": "start",
|
||||
"start-time": "start",
|
||||
"target-user-ids": "users",
|
||||
"time-max": "end",
|
||||
"time-min": "start",
|
||||
"to": "end",
|
||||
"until": "end",
|
||||
"user-id-list": "users",
|
||||
"user-ids": "users",
|
||||
"work-date-begin": "start",
|
||||
"work-date-end": "end",
|
||||
},
|
||||
Blocked: []string{"at-user-ids", "date", "staff-id", "time", "uid", "user", "user-id", "userid"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance +get-self-setting",
|
||||
Aliases: map[string]string{
|
||||
"scene": "setting-scene",
|
||||
"staff-id": "user",
|
||||
"uid": "user",
|
||||
"user-id": "user",
|
||||
"userid": "user",
|
||||
},
|
||||
Blocked: []string{"at-user-ids", "name", "setting-item", "setting-type", "to-user", "user-ids", "users"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance +get-summary",
|
||||
Aliases: map[string]string{
|
||||
"attendance-date": "date",
|
||||
"period": "stats-type",
|
||||
"staff-id": "user",
|
||||
"statistics-type": "stats-type",
|
||||
"summary-type": "stats-type",
|
||||
"uid": "user",
|
||||
"user-id": "user",
|
||||
"userid": "user",
|
||||
},
|
||||
Blocked: []string{"at-user-ids", "end", "end-date", "name", "start", "start-date", "time", "to-user", "user-ids", "users"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance +list-approve",
|
||||
Aliases: map[string]string{
|
||||
"approval-types": "types",
|
||||
"begin": "start",
|
||||
"biz-types": "types",
|
||||
"end-date": "end",
|
||||
"end-time": "end",
|
||||
"from": "start",
|
||||
"from-date": "start",
|
||||
"max-time": "end",
|
||||
"min-time": "start",
|
||||
"since": "start",
|
||||
"staff-ids": "users",
|
||||
"start-date": "start",
|
||||
"start-time": "start",
|
||||
"target-user-ids": "users",
|
||||
"time-max": "end",
|
||||
"time-min": "start",
|
||||
"to": "end",
|
||||
"until": "end",
|
||||
"user-id-list": "users",
|
||||
"user-ids": "users",
|
||||
},
|
||||
Blocked: []string{"at-user-ids", "date", "staff-id", "time", "uid", "user", "user-id", "userid"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance +query-report-data",
|
||||
Aliases: map[string]string{
|
||||
"begin": "start",
|
||||
"end-date": "end",
|
||||
"end-time": "end",
|
||||
"from": "start",
|
||||
"from-date": "start",
|
||||
"max-time": "end",
|
||||
"min-time": "start",
|
||||
"report-column-ids": "columns",
|
||||
"since": "start",
|
||||
"staff-ids": "users",
|
||||
"start-date": "start",
|
||||
"start-time": "start",
|
||||
"target-user-ids": "users",
|
||||
"time-max": "end",
|
||||
"time-min": "start",
|
||||
"to": "end",
|
||||
"until": "end",
|
||||
"user-id-list": "users",
|
||||
"user-ids": "users",
|
||||
},
|
||||
Blocked: []string{"at-user-ids", "column-names", "date", "leave-names", "staff-id", "time", "uid", "user", "user-id", "userid"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance +search-adjustment-rule",
|
||||
Aliases: map[string]string{
|
||||
"current-page": "page",
|
||||
"keyword": "query",
|
||||
"keywords": "query",
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"page-no": "page",
|
||||
"page-num": "page",
|
||||
"page-size": "limit",
|
||||
"per-page": "limit",
|
||||
"q": "query",
|
||||
"search-word": "query",
|
||||
"size": "limit",
|
||||
"take": "limit",
|
||||
"top": "limit",
|
||||
},
|
||||
Blocked: []string{"count", "cursor", "name", "page-index", "page-token", "subject", "text", "title"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance +search-class",
|
||||
Aliases: map[string]string{
|
||||
"class-filter-type": "filter-type",
|
||||
"class-scope": "filter-type",
|
||||
"current-page": "page",
|
||||
"keyword": "query",
|
||||
"keywords": "query",
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"page-no": "page",
|
||||
"page-num": "page",
|
||||
"page-size": "limit",
|
||||
"per-page": "limit",
|
||||
"q": "query",
|
||||
"search-word": "query",
|
||||
"size": "limit",
|
||||
"take": "limit",
|
||||
"top": "limit",
|
||||
},
|
||||
Blocked: []string{"count", "cursor", "name", "page-index", "page-token", "subject", "text", "title"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance +search-group",
|
||||
Aliases: map[string]string{
|
||||
"attendance-group-type": "type",
|
||||
"current-page": "page",
|
||||
"group-type": "type",
|
||||
"include-ble": "query-ble",
|
||||
"include-position": "query-position",
|
||||
"keyword": "query",
|
||||
"keywords": "query",
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"page-no": "page",
|
||||
"page-num": "page",
|
||||
"page-size": "limit",
|
||||
"per-page": "limit",
|
||||
"q": "query",
|
||||
"search-word": "query",
|
||||
"size": "limit",
|
||||
"take": "limit",
|
||||
"top": "limit",
|
||||
},
|
||||
Blocked: []string{"count", "cursor", "name", "page-index", "page-token", "subject", "text", "title"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance +search-overtime-rule",
|
||||
Aliases: map[string]string{
|
||||
"current-page": "page",
|
||||
"keyword": "query",
|
||||
"keywords": "query",
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"page-no": "page",
|
||||
"page-num": "page",
|
||||
"page-size": "limit",
|
||||
"per-page": "limit",
|
||||
"q": "query",
|
||||
"search-word": "query",
|
||||
"size": "limit",
|
||||
"take": "limit",
|
||||
"top": "limit",
|
||||
},
|
||||
Blocked: []string{"count", "cursor", "name", "page-index", "page-token", "subject", "text", "title"},
|
||||
},
|
||||
{
|
||||
CLIPath: "attendance check result",
|
||||
@@ -3912,11 +4226,44 @@ var generatedParamAliases = []ParamAliasEntry{
|
||||
},
|
||||
Blocked: []string{"count", "cursor", "page-index", "page-token"},
|
||||
},
|
||||
{
|
||||
CLIPath: "ding +list",
|
||||
Aliases: map[string]string{
|
||||
"message-type": "type",
|
||||
"next-cursor": "cursor",
|
||||
},
|
||||
Blocked: []string{"next-token", "offset", "page", "page-token", "remind-type", "status"},
|
||||
},
|
||||
{
|
||||
CLIPath: "ding +recall-personal",
|
||||
Aliases: map[string]string{
|
||||
"ding-id": "id",
|
||||
"open-ding-id": "id",
|
||||
},
|
||||
Blocked: []string{"message-id", "request-id", "robot", "robot-code", "task-id", "uuid"},
|
||||
},
|
||||
{
|
||||
CLIPath: "ding +receiver-status",
|
||||
Aliases: map[string]string{
|
||||
"id": "ding-id",
|
||||
"id": "ding-id",
|
||||
"open-ding-id": "ding-id",
|
||||
},
|
||||
Blocked: []string{"message-id", "request-id", "task-id", "uuid"},
|
||||
},
|
||||
{
|
||||
CLIPath: "ding +send-personal",
|
||||
Aliases: map[string]string{
|
||||
"body": "content",
|
||||
"idempotency-key": "uuid",
|
||||
"message-content": "content",
|
||||
"open-dingtalk-ids": "users",
|
||||
"receiver-open-dingtalk-ids": "users",
|
||||
"recipient-open-dingtalk-ids": "users",
|
||||
"remind-type": "type",
|
||||
"request-id": "uuid",
|
||||
"text": "content",
|
||||
},
|
||||
Blocked: []string{"ding-id", "id", "message-type", "name", "open-ding-id", "open-dingtalk-id", "receiver-user-ids", "recipient-user-ids", "robot", "robot-code", "staff-id", "staff-ids", "status", "title", "user", "user-id", "user-ids"},
|
||||
},
|
||||
{
|
||||
CLIPath: "ding message receiver-status",
|
||||
@@ -6005,15 +6352,211 @@ var generatedParamAliases = []ParamAliasEntry{
|
||||
},
|
||||
Blocked: []string{"knowledge-base-id", "limit", "page-size", "part-size", "size", "space", "wiki-workspace-id", "workspace", "workspace-id"},
|
||||
},
|
||||
{
|
||||
CLIPath: "mail +contact-list",
|
||||
Aliases: map[string]string{
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"next-cursor": "cursor",
|
||||
"next-page-token": "cursor",
|
||||
"next-token": "cursor",
|
||||
"page-size": "limit",
|
||||
"page-token": "cursor",
|
||||
"per-page": "limit",
|
||||
"size": "limit",
|
||||
"take": "limit",
|
||||
"top": "limit",
|
||||
},
|
||||
Blocked: []string{"count", "offset", "page"},
|
||||
},
|
||||
{
|
||||
CLIPath: "mail +find-mail-user",
|
||||
Aliases: map[string]string{
|
||||
"keyword": "query",
|
||||
"keywords": "query",
|
||||
"q": "query",
|
||||
"search-word": "query",
|
||||
"keyword": "query",
|
||||
"keywords": "query",
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"next-cursor": "cursor",
|
||||
"next-page-token": "cursor",
|
||||
"next-token": "cursor",
|
||||
"page-size": "limit",
|
||||
"page-token": "cursor",
|
||||
"per-page": "limit",
|
||||
"q": "query",
|
||||
"search-word": "query",
|
||||
"size": "limit",
|
||||
"take": "limit",
|
||||
"top": "limit",
|
||||
},
|
||||
Blocked: []string{"name", "subject", "text", "title"},
|
||||
Blocked: []string{"count", "name", "offset", "page", "subject", "text", "title"},
|
||||
},
|
||||
{
|
||||
CLIPath: "mail +folder-list",
|
||||
Aliases: map[string]string{
|
||||
"folder-id": "folder",
|
||||
},
|
||||
Blocked: []string{"calendar-id", "name", "node-id"},
|
||||
},
|
||||
{
|
||||
CLIPath: "mail +message",
|
||||
Aliases: map[string]string{
|
||||
"mail-id": "id",
|
||||
"message-id": "id",
|
||||
},
|
||||
Blocked: []string{"attachment-id", "conversation-id", "internet-message-id", "thread-id"},
|
||||
},
|
||||
{
|
||||
CLIPath: "mail +messages",
|
||||
Aliases: map[string]string{
|
||||
"mail-ids": "ids",
|
||||
"message-ids": "ids",
|
||||
},
|
||||
Blocked: []string{"conversation-id", "conversation-ids", "mail-id", "message-id", "thread-id", "thread-ids"},
|
||||
},
|
||||
{
|
||||
CLIPath: "mail +recent-mail",
|
||||
Aliases: map[string]string{
|
||||
"folder-id": "folder",
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"next-cursor": "cursor",
|
||||
"next-page-token": "cursor",
|
||||
"next-token": "cursor",
|
||||
"page-size": "limit",
|
||||
"page-token": "cursor",
|
||||
"per-page": "limit",
|
||||
"size": "limit",
|
||||
"take": "limit",
|
||||
"top": "limit",
|
||||
},
|
||||
Blocked: []string{"calendar-id", "count", "name", "node-id", "offset", "page"},
|
||||
},
|
||||
{
|
||||
CLIPath: "mail +search-mail",
|
||||
Aliases: map[string]string{
|
||||
"limit": "size",
|
||||
"max-result": "size",
|
||||
"max-results": "size",
|
||||
"next-cursor": "cursor",
|
||||
"next-page-token": "cursor",
|
||||
"next-token": "cursor",
|
||||
"page-size": "size",
|
||||
"page-token": "cursor",
|
||||
"per-page": "size",
|
||||
"take": "size",
|
||||
"top": "size",
|
||||
},
|
||||
Blocked: []string{"count", "date", "folder", "folder-id", "from", "from-address", "has-attachments", "offset", "page", "sender", "subject", "to-address"},
|
||||
},
|
||||
{
|
||||
CLIPath: "mail +template-list",
|
||||
Aliases: map[string]string{
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"next-cursor": "cursor",
|
||||
"next-page-token": "cursor",
|
||||
"next-token": "cursor",
|
||||
"page-size": "limit",
|
||||
"page-token": "cursor",
|
||||
"per-page": "limit",
|
||||
"size": "limit",
|
||||
"take": "limit",
|
||||
"top": "limit",
|
||||
},
|
||||
Blocked: []string{"count", "offset", "page"},
|
||||
},
|
||||
{
|
||||
CLIPath: "mail +thread",
|
||||
Aliases: map[string]string{
|
||||
"conversation-id": "id",
|
||||
"thread-id": "id",
|
||||
},
|
||||
Blocked: []string{"mail-id", "mail-ids", "message-id", "message-ids"},
|
||||
},
|
||||
{
|
||||
CLIPath: "mail +thread-list",
|
||||
Aliases: map[string]string{
|
||||
"begin": "start",
|
||||
"end-date": "end",
|
||||
"end-time": "end",
|
||||
"folder-id": "folder",
|
||||
"from": "start",
|
||||
"from-date": "start",
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"max-time": "end",
|
||||
"min-time": "start",
|
||||
"next-cursor": "cursor",
|
||||
"next-page-token": "cursor",
|
||||
"next-token": "cursor",
|
||||
"page-size": "limit",
|
||||
"page-token": "cursor",
|
||||
"per-page": "limit",
|
||||
"since": "start",
|
||||
"size": "limit",
|
||||
"start-date": "start",
|
||||
"start-time": "start",
|
||||
"take": "limit",
|
||||
"time-max": "end",
|
||||
"time-min": "start",
|
||||
"top": "limit",
|
||||
},
|
||||
Blocked: []string{"calendar-id", "count", "date", "name", "node-id", "offset", "page", "time"},
|
||||
},
|
||||
{
|
||||
CLIPath: "mail +triage",
|
||||
Aliases: map[string]string{
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"next-cursor": "cursor",
|
||||
"next-page-token": "cursor",
|
||||
"next-token": "cursor",
|
||||
"page-size": "limit",
|
||||
"page-token": "cursor",
|
||||
"per-page": "limit",
|
||||
"size": "limit",
|
||||
"take": "limit",
|
||||
"top": "limit",
|
||||
},
|
||||
Blocked: []string{"count", "date", "folder", "folder-id", "from", "from-address", "has-attachments", "offset", "page", "sender", "subject", "to-address"},
|
||||
},
|
||||
{
|
||||
CLIPath: "mail +unread-mail",
|
||||
Aliases: map[string]string{
|
||||
"limit": "size",
|
||||
"max-result": "size",
|
||||
"max-results": "size",
|
||||
"next-cursor": "cursor",
|
||||
"next-page-token": "cursor",
|
||||
"next-token": "cursor",
|
||||
"page-size": "size",
|
||||
"page-token": "cursor",
|
||||
"per-page": "size",
|
||||
"take": "size",
|
||||
"top": "size",
|
||||
},
|
||||
Blocked: []string{"count", "keyword", "offset", "page", "query", "sender", "subject"},
|
||||
},
|
||||
{
|
||||
CLIPath: "mail +user-search",
|
||||
Aliases: map[string]string{
|
||||
"keywords": "keyword",
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"next-cursor": "cursor",
|
||||
"next-page-token": "cursor",
|
||||
"next-token": "cursor",
|
||||
"page-size": "limit",
|
||||
"page-token": "cursor",
|
||||
"per-page": "limit",
|
||||
"q": "keyword",
|
||||
"query": "keyword",
|
||||
"search-word": "keyword",
|
||||
"size": "limit",
|
||||
"take": "limit",
|
||||
"top": "limit",
|
||||
},
|
||||
Blocked: []string{"count", "name", "offset", "page", "subject", "text", "title"},
|
||||
},
|
||||
{
|
||||
CLIPath: "mail folder update",
|
||||
@@ -6052,6 +6595,123 @@ var generatedParamAliases = []ParamAliasEntry{
|
||||
},
|
||||
Blocked: []string{"name", "subject", "text", "title"},
|
||||
},
|
||||
{
|
||||
CLIPath: "markdown create",
|
||||
Aliases: map[string]string{
|
||||
"body": "content",
|
||||
"dingdrive-space-id": "space-id",
|
||||
"drive-space-id": "space-id",
|
||||
"file-name": "name",
|
||||
"filename": "name",
|
||||
"folder-id": "folder",
|
||||
"knowledge-base-id": "workspace",
|
||||
"local-file": "file",
|
||||
"markdown-content": "content",
|
||||
"plan-only": "dry-run",
|
||||
"preview-only": "dry-run",
|
||||
"source-file": "file",
|
||||
"storage-space-id": "space-id",
|
||||
"text": "content",
|
||||
"wiki-workspace-id": "workspace",
|
||||
},
|
||||
Blocked: []string{"dentry-id", "doc-id", "document-id", "file-id", "node", "node-id", "output-path", "pattern", "regex", "replacement", "title", "url", "version", "version2"},
|
||||
Ambiguous: []string{"content-file", "destination", "id", "path", "space"},
|
||||
},
|
||||
{
|
||||
CLIPath: "markdown diff",
|
||||
Aliases: map[string]string{
|
||||
"base-version": "version",
|
||||
"context-lines": "context",
|
||||
"dentry-uuid": "node",
|
||||
"diff-context": "context",
|
||||
"from-version": "version",
|
||||
"left-version": "version",
|
||||
"local-file": "file",
|
||||
"plan-only": "dry-run",
|
||||
"preview-only": "dry-run",
|
||||
"right-version": "version2",
|
||||
"source-file": "file",
|
||||
"surrounding-lines": "context",
|
||||
"target-version": "version2",
|
||||
"to-version": "version2",
|
||||
"version-no": "version",
|
||||
"version-number": "version",
|
||||
},
|
||||
Blocked: []string{"body", "content", "content-file", "folder", "folder-id", "name", "output-path", "pattern", "regex", "revision", "space-id", "version-id", "workspace", "workspace-id"},
|
||||
Ambiguous: []string{"destination", "left", "path", "right"},
|
||||
},
|
||||
{
|
||||
CLIPath: "markdown fetch",
|
||||
Aliases: map[string]string{
|
||||
"dentry-uuid": "node",
|
||||
"destination-path": "output",
|
||||
"dingdrive-space-id": "space-id",
|
||||
"document-id": "node",
|
||||
"download-to": "output",
|
||||
"drive-space-id": "space-id",
|
||||
"knowledge-base-id": "workspace",
|
||||
"output-file": "output",
|
||||
"output-path": "output",
|
||||
"save-path": "output",
|
||||
"save-to": "output",
|
||||
"storage-space-id": "space-id",
|
||||
"wiki-workspace-id": "workspace",
|
||||
},
|
||||
Blocked: []string{"content", "content-file", "dentry-id", "file", "file-path", "folder", "folder-id", "name", "parent-id", "pattern", "regex", "url", "version", "version2"},
|
||||
Ambiguous: []string{"destination", "path", "space"},
|
||||
},
|
||||
{
|
||||
CLIPath: "markdown overwrite",
|
||||
Aliases: map[string]string{
|
||||
"body": "content",
|
||||
"dentry-uuid": "node",
|
||||
"dingdrive-space-id": "space-id",
|
||||
"document-id": "node",
|
||||
"drive-space-id": "space-id",
|
||||
"file-name": "name",
|
||||
"filename": "name",
|
||||
"knowledge-base-id": "workspace",
|
||||
"local-file": "file",
|
||||
"markdown-content": "content",
|
||||
"no-write": "dry-run",
|
||||
"preview-only": "dry-run",
|
||||
"show-diff": "dry-run",
|
||||
"source-file": "file",
|
||||
"storage-space-id": "space-id",
|
||||
"text": "content",
|
||||
"wiki-workspace-id": "workspace",
|
||||
},
|
||||
Blocked: []string{"content-file", "dentry-id", "folder", "folder-id", "output-path", "parent-id", "pattern", "regex", "replacement", "title", "url", "version", "version2"},
|
||||
Ambiguous: []string{"destination", "id", "path", "space"},
|
||||
},
|
||||
{
|
||||
CLIPath: "markdown patch",
|
||||
Aliases: map[string]string{
|
||||
"dentry-uuid": "node",
|
||||
"dingdrive-space-id": "space-id",
|
||||
"document-id": "node",
|
||||
"drive-space-id": "space-id",
|
||||
"find-text": "pattern",
|
||||
"knowledge-base-id": "workspace",
|
||||
"match-pattern": "pattern",
|
||||
"new-text": "content",
|
||||
"no-write": "dry-run",
|
||||
"old-text": "pattern",
|
||||
"preview-only": "dry-run",
|
||||
"regexp": "regex",
|
||||
"regular-expression": "regex",
|
||||
"replace-with": "content",
|
||||
"replacement": "content",
|
||||
"replacement-text": "content",
|
||||
"search-pattern": "pattern",
|
||||
"show-diff": "dry-run",
|
||||
"storage-space-id": "space-id",
|
||||
"use-regex": "regex",
|
||||
"wiki-workspace-id": "workspace",
|
||||
},
|
||||
Blocked: []string{"content-file", "dentry-id", "file", "file-path", "folder", "folder-id", "name", "output-path", "parent-id", "query", "replacement-file", "url", "version", "version2"},
|
||||
Ambiguous: []string{"body", "destination", "id", "path", "space", "text", "value"},
|
||||
},
|
||||
{
|
||||
CLIPath: "minutes +action-items",
|
||||
Aliases: map[string]string{
|
||||
@@ -6655,6 +7315,128 @@ var generatedParamAliases = []ParamAliasEntry{
|
||||
CLIPath: "minutes upload create",
|
||||
Blocked: []string{"file", "file-path", "path", "template-ids"},
|
||||
},
|
||||
{
|
||||
CLIPath: "oa +list-cc",
|
||||
Aliases: map[string]string{
|
||||
"current-page": "page",
|
||||
"keyword": "query",
|
||||
"keywords": "query",
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"page-no": "page",
|
||||
"page-num": "page",
|
||||
"page-size": "limit",
|
||||
"per-page": "limit",
|
||||
"q": "query",
|
||||
"search-word": "query",
|
||||
"size": "limit",
|
||||
"take": "limit",
|
||||
"top": "limit",
|
||||
},
|
||||
Blocked: []string{"count", "cursor", "name", "page-index", "page-token", "subject", "text", "title"},
|
||||
},
|
||||
{
|
||||
CLIPath: "oa +list-executed",
|
||||
Aliases: map[string]string{
|
||||
"current-page": "page",
|
||||
"keyword": "query",
|
||||
"keywords": "query",
|
||||
"page-no": "page",
|
||||
"page-num": "page",
|
||||
"q": "query",
|
||||
"search-word": "query",
|
||||
},
|
||||
Blocked: []string{"count", "cursor", "name", "page-index", "page-size", "page-token", "subject", "text", "title"},
|
||||
},
|
||||
{
|
||||
CLIPath: "oa +list-forms",
|
||||
Aliases: map[string]string{
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"next-cursor": "cursor",
|
||||
"next-page-token": "cursor",
|
||||
"next-token": "cursor",
|
||||
"page-size": "limit",
|
||||
"page-token": "cursor",
|
||||
"per-page": "limit",
|
||||
"size": "limit",
|
||||
"take": "limit",
|
||||
"top": "limit",
|
||||
},
|
||||
Blocked: []string{"count", "offset", "page"},
|
||||
},
|
||||
{
|
||||
CLIPath: "oa +list-pending",
|
||||
Aliases: map[string]string{
|
||||
"begin": "start",
|
||||
"current-page": "page",
|
||||
"end-date": "end",
|
||||
"end-time": "end",
|
||||
"from": "start",
|
||||
"from-date": "start",
|
||||
"keyword": "query",
|
||||
"keywords": "query",
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"max-time": "end",
|
||||
"min-time": "start",
|
||||
"page-no": "page",
|
||||
"page-num": "page",
|
||||
"page-size": "limit",
|
||||
"per-page": "limit",
|
||||
"q": "query",
|
||||
"search-word": "query",
|
||||
"since": "start",
|
||||
"size": "limit",
|
||||
"start-date": "start",
|
||||
"start-time": "start",
|
||||
"take": "limit",
|
||||
"time-max": "end",
|
||||
"time-min": "start",
|
||||
"top": "limit",
|
||||
},
|
||||
Blocked: []string{"count", "cursor", "date", "name", "offset", "page-index", "page-token", "subject", "text", "time", "title"},
|
||||
},
|
||||
{
|
||||
CLIPath: "oa +list-submitted",
|
||||
Aliases: map[string]string{
|
||||
"current-page": "page",
|
||||
"keyword": "query",
|
||||
"keywords": "query",
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"page-no": "page",
|
||||
"page-num": "page",
|
||||
"page-size": "limit",
|
||||
"per-page": "limit",
|
||||
"q": "query",
|
||||
"search-word": "query",
|
||||
"size": "limit",
|
||||
"take": "limit",
|
||||
"top": "limit",
|
||||
},
|
||||
Blocked: []string{"count", "cursor", "name", "page-index", "page-token", "subject", "text", "title"},
|
||||
},
|
||||
{
|
||||
CLIPath: "oa +my-initiated",
|
||||
Aliases: map[string]string{
|
||||
"current-page": "page",
|
||||
"keyword": "query",
|
||||
"keywords": "query",
|
||||
"max-result": "limit",
|
||||
"max-results": "limit",
|
||||
"page-no": "page",
|
||||
"page-num": "page",
|
||||
"page-size": "limit",
|
||||
"per-page": "limit",
|
||||
"q": "query",
|
||||
"search-word": "query",
|
||||
"size": "limit",
|
||||
"take": "limit",
|
||||
"top": "limit",
|
||||
},
|
||||
Blocked: []string{"count", "cursor", "name", "page-index", "page-token", "subject", "text", "title"},
|
||||
},
|
||||
{
|
||||
CLIPath: "oa +search-forms",
|
||||
Aliases: map[string]string{
|
||||
@@ -6675,6 +7457,100 @@ var generatedParamAliases = []ParamAliasEntry{
|
||||
},
|
||||
Blocked: []string{"name", "subject", "text", "title"},
|
||||
},
|
||||
{
|
||||
CLIPath: "report +inbox-list",
|
||||
Aliases: map[string]string{
|
||||
"begin": "start",
|
||||
"end-date": "end",
|
||||
"end-time": "end",
|
||||
"from": "start",
|
||||
"from-date": "start",
|
||||
"from-staff-ids": "sender-user-ids",
|
||||
"limit": "size",
|
||||
"max-result": "size",
|
||||
"max-results": "size",
|
||||
"max-time": "end",
|
||||
"min-time": "start",
|
||||
"page-size": "size",
|
||||
"per-page": "size",
|
||||
"sender-staff-ids": "sender-user-ids",
|
||||
"since": "start",
|
||||
"start-date": "start",
|
||||
"start-time": "start",
|
||||
"take": "size",
|
||||
"time-max": "end",
|
||||
"time-min": "start",
|
||||
"top": "size",
|
||||
},
|
||||
Blocked: []string{"count", "date", "page", "sender-id", "sender-staff-id", "sender-user-id", "staff-id", "staff-ids", "time", "user-id", "user-ids"},
|
||||
},
|
||||
{
|
||||
CLIPath: "report +outbox-list",
|
||||
Aliases: map[string]string{
|
||||
"begin": "start",
|
||||
"end-date": "end",
|
||||
"end-time": "end",
|
||||
"from": "start",
|
||||
"from-date": "start",
|
||||
"limit": "size",
|
||||
"max-result": "size",
|
||||
"max-results": "size",
|
||||
"max-time": "end",
|
||||
"min-time": "start",
|
||||
"modified-from": "modified-start",
|
||||
"modified-to": "modified-end",
|
||||
"name": "template-name",
|
||||
"page-size": "size",
|
||||
"per-page": "size",
|
||||
"report-template-name": "template-name",
|
||||
"since": "start",
|
||||
"start-date": "start",
|
||||
"start-time": "start",
|
||||
"take": "size",
|
||||
"time-max": "end",
|
||||
"time-min": "start",
|
||||
"top": "size",
|
||||
"update-end": "modified-end",
|
||||
"update-start": "modified-start",
|
||||
"updated-end": "modified-end",
|
||||
"updated-start": "modified-start",
|
||||
},
|
||||
Blocked: []string{"count", "date", "page", "report-template-id", "template-id", "template-type", "time"},
|
||||
Ambiguous: []string{"template"},
|
||||
},
|
||||
{
|
||||
CLIPath: "report +report-latest",
|
||||
Aliases: map[string]string{
|
||||
"begin": "start",
|
||||
"end-date": "end",
|
||||
"end-time": "end",
|
||||
"from": "start",
|
||||
"from-date": "start",
|
||||
"max-time": "end",
|
||||
"min-time": "start",
|
||||
"name": "keyword",
|
||||
"report-template-name": "keyword",
|
||||
"since": "start",
|
||||
"start-date": "start",
|
||||
"start-time": "start",
|
||||
"template-name": "keyword",
|
||||
"time-max": "end",
|
||||
"time-min": "start",
|
||||
},
|
||||
Blocked: []string{"date", "modified-end", "modified-start", "report-template-id", "template-id", "time"},
|
||||
Ambiguous: []string{"template"},
|
||||
},
|
||||
{
|
||||
CLIPath: "report +template-search",
|
||||
Aliases: map[string]string{
|
||||
"keyword": "query",
|
||||
"keywords": "query",
|
||||
"q": "query",
|
||||
"search-word": "query",
|
||||
},
|
||||
Blocked: []string{"report-template-id", "subject", "template-id", "text", "title"},
|
||||
Ambiguous: []string{"name", "template"},
|
||||
},
|
||||
{
|
||||
CLIPath: "report list",
|
||||
Aliases: map[string]string{
|
||||
@@ -6693,6 +7569,33 @@ var generatedParamAliases = []ParamAliasEntry{
|
||||
CLIPath: "report outbox list",
|
||||
Blocked: []string{"template-type"},
|
||||
},
|
||||
{
|
||||
CLIPath: "sheet +list-sheets",
|
||||
Aliases: map[string]string{
|
||||
"dentry-uuid": "node",
|
||||
"doc": "node",
|
||||
"doc-id": "node",
|
||||
"document-id": "node",
|
||||
"file-id": "node",
|
||||
"node-id": "node",
|
||||
"url": "node",
|
||||
},
|
||||
Blocked: []string{"block-id", "comment-id", "comment-key", "dentry-id", "folder", "folder-id", "id", "job-id", "name", "parent-id", "revision", "role", "space-id", "task-id", "template-id", "version", "workspace", "workspace-id"},
|
||||
},
|
||||
{
|
||||
CLIPath: "sheet +read",
|
||||
Aliases: map[string]string{
|
||||
"dentry-uuid": "node",
|
||||
"doc": "node",
|
||||
"doc-id": "node",
|
||||
"document-id": "node",
|
||||
"file-id": "node",
|
||||
"node-id": "node",
|
||||
"url": "node",
|
||||
"worksheet-id": "sheet-id",
|
||||
},
|
||||
Blocked: []string{"a1-ranges", "block-id", "cell-ranges", "comment-id", "comment-key", "dentry-id", "destination-range", "folder", "folder-id", "id", "job-id", "name", "parent-id", "ranges", "revision", "role", "source-range", "source-sheet-id", "space-id", "start-cell", "target-range", "target-sheet-id", "task-id", "template-id", "title", "version", "workspace", "workspace-id"},
|
||||
},
|
||||
{
|
||||
CLIPath: "todo +assign",
|
||||
Aliases: map[string]string{
|
||||
|
||||
+819
-644
File diff suppressed because one or more lines are too long
@@ -10,6 +10,8 @@ import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -39,7 +41,16 @@ func TestMain(m *testing.M) {
|
||||
if os.Getenv(helpersShellStubEnv) == "1" {
|
||||
os.Exit(runHelpersShellStub())
|
||||
}
|
||||
// Cobra's Windows pre-exec hook walks the process table on every Execute*
|
||||
// call to detect Explorer launches. Helpers tests execute command trees
|
||||
// thousands of times, and none of those in-process invocations can be an
|
||||
// Explorer launch, so keep that production-only check out of the test
|
||||
// process. This also prevents every new exhaustive harness from having to
|
||||
// remember a test-local override.
|
||||
originalMousetrapHelpText := cobra.MousetrapHelpText
|
||||
cobra.MousetrapHelpText = ""
|
||||
code := m.Run()
|
||||
cobra.MousetrapHelpText = originalMousetrapHelpText
|
||||
if helpersShellStubBaseDir != "" {
|
||||
_ = os.RemoveAll(helpersShellStubBaseDir)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ const (
|
||||
|
||||
CommandMigrationMove = "command_move"
|
||||
CommandMigrationFlagExtraction = "flag_extraction"
|
||||
CommandMigrationAvailability = "schema_availability_hardening"
|
||||
)
|
||||
|
||||
// CommandMigrationManifest governs compatibility-preserving surface moves that
|
||||
@@ -64,6 +65,12 @@ type CommandMigrationSchema struct {
|
||||
SourceToolID string `json:"source_tool_id"`
|
||||
ReplacementToolID string `json:"replacement_tool_id"`
|
||||
Parameters []CommandParameterMigration `json:"parameters"`
|
||||
Availability *CommandAvailabilityChange `json:"availability,omitempty"`
|
||||
}
|
||||
|
||||
type CommandAvailabilityChange struct {
|
||||
Before string `json:"before"`
|
||||
After string `json:"after"`
|
||||
}
|
||||
|
||||
type CommandParameterMigration struct {
|
||||
@@ -126,14 +133,23 @@ func (m CommandMigrationManifest) Validate() error {
|
||||
}
|
||||
|
||||
func (m CommandMigration) validate() error {
|
||||
if m.Kind != CommandMigrationMove && m.Kind != CommandMigrationFlagExtraction {
|
||||
if m.Kind != CommandMigrationMove && m.Kind != CommandMigrationFlagExtraction && m.Kind != CommandMigrationAvailability {
|
||||
return fmt.Errorf("invalid kind %q", m.Kind)
|
||||
}
|
||||
if !isExactCommandPath(m.Legacy.Command) || !isExactCommandPath(m.Replacement.Command) {
|
||||
return fmt.Errorf("legacy and replacement must be exact command paths rooted at dws")
|
||||
}
|
||||
if m.Legacy.Command == m.Replacement.Command {
|
||||
return fmt.Errorf("legacy and replacement command paths must differ")
|
||||
if m.Kind == CommandMigrationAvailability {
|
||||
if !isExactCommandPath(m.Legacy.Command) {
|
||||
return fmt.Errorf("legacy must be an exact command path rooted at dws")
|
||||
}
|
||||
if m.Replacement != (CommandMigrationSide{}) {
|
||||
return fmt.Errorf("schema_availability_hardening must not declare replacement")
|
||||
}
|
||||
} else {
|
||||
if !isExactCommandPath(m.Legacy.Command) || !isExactCommandPath(m.Replacement.Command) {
|
||||
return fmt.Errorf("legacy and replacement must be exact command paths rooted at dws")
|
||||
}
|
||||
if m.Legacy.Command == m.Replacement.Command {
|
||||
return fmt.Errorf("legacy and replacement command paths must differ")
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(m.Reason) == "" || m.Reason != strings.TrimSpace(m.Reason) {
|
||||
return fmt.Errorf("migration must include a non-empty trimmed reason")
|
||||
@@ -141,12 +157,15 @@ func (m CommandMigration) validate() error {
|
||||
if m.State != CommandMigrationPending && m.State != CommandMigrationConsumed {
|
||||
return fmt.Errorf("invalid state %q", m.State)
|
||||
}
|
||||
for label, state := range map[string]CommandMigrationState{
|
||||
"legacy before": m.Legacy.Before,
|
||||
"legacy after": m.Legacy.After,
|
||||
"replacement before": m.Replacement.Before,
|
||||
"replacement after": m.Replacement.After,
|
||||
} {
|
||||
states := map[string]CommandMigrationState{
|
||||
"legacy before": m.Legacy.Before,
|
||||
"legacy after": m.Legacy.After,
|
||||
}
|
||||
if m.Kind != CommandMigrationAvailability {
|
||||
states["replacement before"] = m.Replacement.Before
|
||||
states["replacement after"] = m.Replacement.After
|
||||
}
|
||||
for label, state := range states {
|
||||
if err := state.validate(label); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -155,7 +174,8 @@ func (m CommandMigration) validate() error {
|
||||
!m.Legacy.After.Present || !m.Legacy.After.Runnable {
|
||||
return fmt.Errorf("legacy command must remain runnable and start visible")
|
||||
}
|
||||
if m.Replacement.Before.Present || !m.Replacement.After.Present || !m.Replacement.After.Runnable || m.Replacement.After.Hidden {
|
||||
if m.Kind != CommandMigrationAvailability &&
|
||||
(m.Replacement.Before.Present || !m.Replacement.After.Present || !m.Replacement.After.Runnable || m.Replacement.After.Hidden) {
|
||||
return fmt.Errorf("replacement command must migrate exactly from absent to visible runnable")
|
||||
}
|
||||
if err := m.Schema.validate(m.Kind); err != nil {
|
||||
@@ -214,6 +234,13 @@ func (m CommandMigration) validate() error {
|
||||
if m.LegacyFlag.Before.NoOpt != wantNoOpt || m.LegacyFlag.After.NoOpt != wantNoOpt {
|
||||
return fmt.Errorf("flag_extraction legacy_flag no_opt must equal replacement constant %q", wantNoOpt)
|
||||
}
|
||||
case CommandMigrationAvailability:
|
||||
if !m.Legacy.After.Hidden {
|
||||
return fmt.Errorf("schema_availability_hardening legacy command must migrate exactly from visible to hidden")
|
||||
}
|
||||
if m.LegacyFlag != (CommandMigrationFlag{}) {
|
||||
return fmt.Errorf("schema_availability_hardening must not declare legacy_flag")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -259,9 +286,8 @@ func (f CommandMigrationFlag) validate() error {
|
||||
|
||||
func (s CommandMigrationSchema) validate(kind string) error {
|
||||
for label, value := range map[string]string{
|
||||
"product_id": s.ProductID,
|
||||
"source_tool_id": s.SourceToolID,
|
||||
"replacement_tool_id": s.ReplacementToolID,
|
||||
"product_id": s.ProductID,
|
||||
"source_tool_id": s.SourceToolID,
|
||||
} {
|
||||
if !isExactSchemaIdentifier(value) {
|
||||
return fmt.Errorf("schema %s must be an exact identifier", label)
|
||||
@@ -270,6 +296,21 @@ func (s CommandMigrationSchema) validate(kind string) error {
|
||||
if s.Parameters == nil {
|
||||
return fmt.Errorf("schema parameters must be an array")
|
||||
}
|
||||
if kind == CommandMigrationAvailability {
|
||||
if s.ReplacementToolID != "" || len(s.Parameters) != 0 {
|
||||
return fmt.Errorf("schema_availability_hardening must not declare a replacement tool or parameter mappings")
|
||||
}
|
||||
if s.Availability == nil || s.Availability.Before != "available" || s.Availability.After != "unavailable" {
|
||||
return fmt.Errorf("schema_availability_hardening requires availability available to unavailable")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !isExactSchemaIdentifier(s.ReplacementToolID) {
|
||||
return fmt.Errorf("schema replacement_tool_id must be an exact identifier")
|
||||
}
|
||||
if s.Availability != nil {
|
||||
return fmt.Errorf("%s must not declare schema availability", kind)
|
||||
}
|
||||
seenFrom := map[string]bool{}
|
||||
seenTo := map[string]bool{}
|
||||
seenConstantProperty := map[string]bool{}
|
||||
@@ -344,6 +385,9 @@ func (m CommandMigration) key() string {
|
||||
}
|
||||
|
||||
func (m CommandMigration) displayKey() string {
|
||||
if m.Kind == CommandMigrationAvailability {
|
||||
return fmt.Sprintf("%s %q", m.Kind, m.Legacy.Command)
|
||||
}
|
||||
return fmt.Sprintf("%s %q -> %q", m.Kind, m.Legacy.Command, m.Replacement.Command)
|
||||
}
|
||||
|
||||
@@ -401,7 +445,7 @@ func AuthorizeCommandMigrations(
|
||||
func validateCommandMoveLegacyLeaves(snapshot Snapshot, manifest CommandMigrationManifest) error {
|
||||
commands := commandIndex(snapshot)
|
||||
for _, migration := range manifest.Migrations {
|
||||
if migration.Kind != CommandMigrationMove {
|
||||
if migration.Kind != CommandMigrationMove && migration.Kind != CommandMigrationAvailability {
|
||||
continue
|
||||
}
|
||||
if _, present := commands[migration.Legacy.Command]; !present {
|
||||
@@ -409,7 +453,7 @@ func validateCommandMoveLegacyLeaves(snapshot Snapshot, manifest CommandMigratio
|
||||
}
|
||||
for _, command := range snapshot.Commands {
|
||||
if strings.HasPrefix(command.Path, migration.Legacy.Command+" ") {
|
||||
return fmt.Errorf("command_move legacy command %q must be a leaf", migration.Legacy.Command)
|
||||
return fmt.Errorf("%s legacy command %q must be a leaf", migration.Kind, migration.Legacy.Command)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -524,6 +568,15 @@ func sameCommandMigrationApproval(left, right CommandMigration) bool {
|
||||
func matchCommandMigrationPhase(snapshot Snapshot, migration CommandMigration) commandMigrationPhase {
|
||||
commands := commandIndex(snapshot)
|
||||
legacy := commandMigrationStateForCommand(commands, migration.Legacy.Command)
|
||||
if migration.Kind == CommandMigrationAvailability {
|
||||
if legacy == migration.Legacy.Before {
|
||||
return commandMigrationBefore
|
||||
}
|
||||
if legacy == migration.Legacy.After {
|
||||
return commandMigrationAfter
|
||||
}
|
||||
return commandMigrationPartial
|
||||
}
|
||||
replacement := commandMigrationStateForCommand(commands, migration.Replacement.Command)
|
||||
before := legacy == migration.Legacy.Before && replacement == migration.Replacement.Before
|
||||
after := legacy == migration.Legacy.After && replacement == migration.Replacement.After
|
||||
@@ -629,6 +682,10 @@ func commandMigrationAuthorizesChange(current, reference Snapshot, change Change
|
||||
if change.Kind == "command_became_hidden" && change.Flag == "" {
|
||||
return true
|
||||
}
|
||||
case CommandMigrationAvailability:
|
||||
if change.Kind == "command_became_hidden" && change.Flag == "" {
|
||||
return true
|
||||
}
|
||||
case CommandMigrationFlagExtraction:
|
||||
if change.Kind == "flag_became_hidden" && change.Flag == migration.LegacyFlag.Name {
|
||||
return true
|
||||
|
||||
@@ -186,6 +186,7 @@ func TestCrossPlatformCoverageCommandMigrationValidationEdges(t *testing.T) {
|
||||
want string
|
||||
}{
|
||||
{"identifier", CommandMigrationMove, func(s *CommandMigrationSchema) { s.ProductID = "bad/id" }, "exact identifier"},
|
||||
{"replacement identifier", CommandMigrationMove, func(s *CommandMigrationSchema) { s.ReplacementToolID = "bad/id" }, "replacement_tool_id"},
|
||||
{"nil parameters", CommandMigrationMove, func(s *CommandMigrationSchema) { s.Parameters = nil }, "must be an array"},
|
||||
{"bad from", CommandMigrationMove, func(s *CommandMigrationSchema) { s.Parameters[0].From = "bad name" }, "exact parameter"},
|
||||
{"duplicate from", CommandMigrationMove, func(s *CommandMigrationSchema) { s.Parameters = append(s.Parameters, s.Parameters[0]) }, "duplicates from"},
|
||||
@@ -241,6 +242,96 @@ func TestCrossPlatformCoverageCommandMigrationValidationEdges(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageSchemaAvailabilityMigrationLifecycle(t *testing.T) {
|
||||
pending := availabilityCommandMigrationManifest(CommandMigrationPending)
|
||||
consumed := availabilityCommandMigrationManifest(CommandMigrationConsumed)
|
||||
empty := CommandMigrationManifest{Version: CommandMigrationManifestVersion, Migrations: []CommandMigration{}}
|
||||
before := testSnapshot(
|
||||
testCommand("dws"),
|
||||
testCommand("dws devapp"),
|
||||
testCommand("dws devapp +event-list"),
|
||||
)
|
||||
after := testSnapshot(
|
||||
testCommand("dws"),
|
||||
testCommand("dws devapp"),
|
||||
Command{Path: "dws devapp +event-list", Runnable: true, Hidden: true},
|
||||
)
|
||||
|
||||
got, err := AuthorizeCommandMigrations(
|
||||
after,
|
||||
map[string]Snapshot{"merge-base": before, "stable": before},
|
||||
pending,
|
||||
consumed,
|
||||
)
|
||||
if err != nil || len(got) != 1 {
|
||||
t.Fatalf("availability lifecycle authorizations=%#v error=%v", got, err)
|
||||
}
|
||||
report, err := CompareAllWithInterfaceMigrations(
|
||||
after,
|
||||
map[string]Snapshot{"merge-base": before, "stable": before},
|
||||
FlagMigrationManifest{Version: FlagMigrationManifestVersion, Migrations: []FlagMigration{}},
|
||||
FlagMigrationManifest{Version: FlagMigrationManifestVersion, Migrations: []FlagMigration{}},
|
||||
pending,
|
||||
consumed,
|
||||
)
|
||||
if err != nil || !report.Compatible {
|
||||
t.Fatalf("availability interface report compatible=%v error=%v report=%#v", report.Compatible, err, report)
|
||||
}
|
||||
if got, err := AuthorizeCommandMigrations(
|
||||
before,
|
||||
map[string]Snapshot{"merge-base": before, "stable": before},
|
||||
empty,
|
||||
pending,
|
||||
); err != nil || len(got) != 0 {
|
||||
t.Fatalf("candidate-added pending migration must remain inert: %#v, %v", got, err)
|
||||
}
|
||||
|
||||
valid := pending.Migrations[0]
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
mutate func(*CommandMigration)
|
||||
want string
|
||||
}{
|
||||
{"legacy path", func(m *CommandMigration) { m.Legacy.Command = "dws devapp *" }, "legacy must be an exact"},
|
||||
{"replacement", func(m *CommandMigration) { m.Replacement.Command = "dws devapp other" }, "must not declare replacement"},
|
||||
{"visible after", func(m *CommandMigration) { m.Legacy.After.Hidden = false }, "visible to hidden"},
|
||||
{"legacy flag", func(m *CommandMigration) { m.LegacyFlag.Name = "legacy" }, "must not declare legacy_flag"},
|
||||
{"missing availability", func(m *CommandMigration) { m.Schema.Availability = nil }, "requires availability"},
|
||||
{"wrong availability", func(m *CommandMigration) { m.Schema.Availability.After = "available" }, "requires availability"},
|
||||
{"parameter mapping", func(m *CommandMigration) {
|
||||
m.Schema.Parameters = []CommandParameterMigration{{From: "cursor", To: "next-token"}}
|
||||
}, "must not declare"},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
migration := cloneCommandMigration(valid)
|
||||
if valid.Schema.Availability != nil {
|
||||
change := *valid.Schema.Availability
|
||||
migration.Schema.Availability = &change
|
||||
}
|
||||
test.mutate(&migration)
|
||||
if err := migration.validate(); err == nil || !strings.Contains(err.Error(), test.want) {
|
||||
t.Fatalf("availability migration validation error=%v, want %q", err, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
if got := valid.displayKey(); !strings.Contains(got, CommandMigrationAvailability) {
|
||||
t.Fatalf("availability display key = %q", got)
|
||||
}
|
||||
if got := matchCommandMigrationPhase(testSnapshot(testCommand("dws")), valid); got != commandMigrationPartial {
|
||||
t.Fatalf("missing availability command phase = %q", got)
|
||||
}
|
||||
parent := before
|
||||
parent.Commands = append(append([]Command(nil), before.Commands...), testCommand("dws devapp +event-list detail"))
|
||||
if err := validateCommandMoveLegacyLeaves(parent, pending); err == nil || !strings.Contains(err.Error(), "must be a leaf") {
|
||||
t.Fatalf("availability parent command error = %v", err)
|
||||
}
|
||||
moveSchema := commandMigrationManifest(CommandMigrationPending).Migrations[0].Schema
|
||||
moveSchema.Availability = &CommandAvailabilityChange{Before: "available", After: "unavailable"}
|
||||
if err := moveSchema.validate(CommandMigrationMove); err == nil || !strings.Contains(err.Error(), "must not declare schema availability") {
|
||||
t.Fatalf("ordinary move availability error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageFlagExtractionRejectsRequiredLegacyFlag(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
@@ -686,7 +777,10 @@ func flagExtractionCommandMigrationManifest(state string) CommandMigrationManife
|
||||
|
||||
func cloneCommandMigration(source CommandMigration) CommandMigration {
|
||||
cloned := source
|
||||
cloned.Schema.Parameters = append([]CommandParameterMigration(nil), source.Schema.Parameters...)
|
||||
if source.Schema.Parameters != nil {
|
||||
cloned.Schema.Parameters = make([]CommandParameterMigration, len(source.Schema.Parameters))
|
||||
copy(cloned.Schema.Parameters, source.Schema.Parameters)
|
||||
}
|
||||
for index, parameter := range source.Schema.Parameters {
|
||||
if parameter.ReplacementConstant == nil {
|
||||
continue
|
||||
@@ -703,6 +797,28 @@ func singleCommandMigrationManifest(state string) CommandMigrationManifest {
|
||||
return manifest
|
||||
}
|
||||
|
||||
func availabilityCommandMigrationManifest(state string) CommandMigrationManifest {
|
||||
return CommandMigrationManifest{
|
||||
Version: CommandMigrationManifestVersion,
|
||||
Migrations: []CommandMigration{{
|
||||
Kind: CommandMigrationAvailability,
|
||||
Legacy: CommandMigrationSide{
|
||||
Command: "dws devapp +event-list",
|
||||
Before: CommandMigrationState{Present: true, Runnable: true},
|
||||
After: CommandMigrationState{Present: true, Runnable: true, Hidden: true},
|
||||
},
|
||||
Schema: CommandMigrationSchema{
|
||||
ProductID: "devapp",
|
||||
SourceToolID: "devapp.shortcut_event_list",
|
||||
Parameters: []CommandParameterMigration{},
|
||||
Availability: &CommandAvailabilityChange{Before: "available", After: "unavailable"},
|
||||
},
|
||||
State: state,
|
||||
Reason: "Fail closed until terminal pagination evidence is available.",
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func modifiedCommandManifest(source CommandMigrationManifest) CommandMigrationManifest {
|
||||
modified := source
|
||||
modified.Migrations = append([]CommandMigration(nil), source.Migrations...)
|
||||
|
||||
@@ -14,7 +14,7 @@ usage() {
|
||||
# Single source of truth for the partition set. CI runs one job per partition and
|
||||
# pins its shard names to this list, so a name that appears here without a
|
||||
# dispatch entry below fails closed rather than silently skipping tests.
|
||||
APP_PARTITIONS='schema a-b c-a-l c-m-r c-s-z c-other d-r s-z-example-fuzz'
|
||||
APP_PARTITIONS='schema a-b c-a-l c-m-o c-p-r c-s-z c-other d-r s-z-example-fuzz'
|
||||
|
||||
mode="${1:-}"
|
||||
partition=""
|
||||
@@ -67,7 +67,8 @@ fi
|
||||
schema_count=0
|
||||
ab_count=0
|
||||
cal_count=0
|
||||
cmr_count=0
|
||||
cmo_count=0
|
||||
cpr_count=0
|
||||
csz_count=0
|
||||
cother_count=0
|
||||
dr_count=0
|
||||
@@ -79,7 +80,8 @@ while IFS= read -r test_name; do
|
||||
Test*Schema*) schema_count=$((schema_count + 1)) ;;
|
||||
Test[A-B]*) ab_count=$((ab_count + 1)) ;;
|
||||
TestCrossPlatformCoverage[A-L]*) cal_count=$((cal_count + 1)) ;;
|
||||
TestCrossPlatformCoverage[M-R]*) cmr_count=$((cmr_count + 1)) ;;
|
||||
TestCrossPlatformCoverage[M-O]*) cmo_count=$((cmo_count + 1)) ;;
|
||||
TestCrossPlatformCoverage[P-R]*) cpr_count=$((cpr_count + 1)) ;;
|
||||
TestCrossPlatformCoverage[S-Z]*) csz_count=$((csz_count + 1)) ;;
|
||||
TestC*) cother_count=$((cother_count + 1)) ;;
|
||||
Test[D-R]*) dr_count=$((dr_count + 1)) ;;
|
||||
@@ -103,7 +105,8 @@ for spec in \
|
||||
"schema:$schema_count" \
|
||||
"a-b:$ab_count" \
|
||||
"c-a-l:$cal_count" \
|
||||
"c-m-r:$cmr_count" \
|
||||
"c-m-o:$cmo_count" \
|
||||
"c-p-r:$cpr_count" \
|
||||
"c-s-z:$csz_count" \
|
||||
"c-other:$cother_count" \
|
||||
"d-r:$dr_count" \
|
||||
@@ -142,14 +145,14 @@ for name in $classified; do
|
||||
done
|
||||
|
||||
total_count="$(wc -l < "$tests" | tr -d ' ')"
|
||||
assigned_count=$((schema_count + ab_count + cal_count + cmr_count + csz_count + cother_count + dr_count + sz_count))
|
||||
assigned_count=$((schema_count + ab_count + cal_count + cmo_count + cpr_count + csz_count + cother_count + dr_count + sz_count))
|
||||
if [ "$assigned_count" -ne "$total_count" ]; then
|
||||
printf 'app race partitions assigned %s tests, want %s\n' "$assigned_count" "$total_count" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'app race partitions cover %s top-level tests exactly once: schema=%s a-b=%s c-a-l=%s c-m-r=%s c-s-z=%s c-other=%s d-r=%s s-z-example-fuzz=%s\n' \
|
||||
"$total_count" "$schema_count" "$ab_count" "$cal_count" "$cmr_count" "$csz_count" "$cother_count" "$dr_count" "$sz_count"
|
||||
printf 'app race partitions cover %s top-level tests exactly once: schema=%s a-b=%s c-a-l=%s c-m-o=%s c-p-r=%s c-s-z=%s c-other=%s d-r=%s s-z-example-fuzz=%s\n' \
|
||||
"$total_count" "$schema_count" "$ab_count" "$cal_count" "$cmo_count" "$cpr_count" "$csz_count" "$cother_count" "$dr_count" "$sz_count"
|
||||
|
||||
if [ "$mode" = "verify" ]; then
|
||||
exit 0
|
||||
@@ -208,7 +211,8 @@ run_named_partition() {
|
||||
schema) run_partition schema no-race "$schema_pattern" ;;
|
||||
a-b) run_partition a-b race '^Test[A-B]' "$schema_pattern" ;;
|
||||
c-a-l) run_partition c-a-l race '^TestCrossPlatformCoverage[A-L]' "$schema_pattern" ;;
|
||||
c-m-r) run_partition c-m-r race '^TestCrossPlatformCoverage[M-R]' "$schema_pattern" ;;
|
||||
c-m-o) run_partition c-m-o race '^TestCrossPlatformCoverage[M-O]' "$schema_pattern" ;;
|
||||
c-p-r) run_partition c-p-r race '^TestCrossPlatformCoverage[P-R]' "$schema_pattern" ;;
|
||||
c-s-z) run_partition c-s-z race '^TestCrossPlatformCoverage[S-Z]' "$schema_pattern" ;;
|
||||
c-other) run_partition c-other race '^TestC' '^Test.*Schema|^TestCrossPlatformCoverage' ;;
|
||||
d-r) run_partition d-r race '^Test[D-R]' "$schema_pattern" ;;
|
||||
|
||||
@@ -13,6 +13,69 @@ import (
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/interfacesnapshot"
|
||||
)
|
||||
|
||||
func TestCrossPlatformCoverageSchemaAvailabilityMigrationNormalizesOnlyAvailability(t *testing.T) {
|
||||
baseline := baselineContract()
|
||||
current := cloneContract(baseline)
|
||||
mutateTool(¤t, func(tool *toolSchema) { tool.Availability = "unavailable" })
|
||||
migration := interfacesnapshot.CommandMigration{
|
||||
Kind: interfacesnapshot.CommandMigrationAvailability,
|
||||
Legacy: interfacesnapshot.CommandMigrationSide{
|
||||
Command: "dws doc create",
|
||||
Before: interfacesnapshot.CommandMigrationState{Present: true, Runnable: true},
|
||||
After: interfacesnapshot.CommandMigrationState{Present: true, Runnable: true, Hidden: true},
|
||||
},
|
||||
Schema: interfacesnapshot.CommandMigrationSchema{
|
||||
ProductID: "doc",
|
||||
SourceToolID: "doc.create",
|
||||
Parameters: []interfacesnapshot.CommandParameterMigration{},
|
||||
Availability: &interfacesnapshot.CommandAvailabilityChange{Before: "available", After: "unavailable"},
|
||||
},
|
||||
State: interfacesnapshot.CommandMigrationPending,
|
||||
Reason: "Reviewed fail-closed availability hardening.",
|
||||
}
|
||||
|
||||
normalized, err := normalizeSchemaCommandMigrations(baseline, current, []interfacesnapshot.CommandMigration{migration})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := normalized.Products["doc"].Tools["doc.create"].Availability; got != "unavailable" {
|
||||
t.Fatalf("normalized availability = %q", got)
|
||||
}
|
||||
if failures := checkCompatibility(normalized, current); len(failures) != 0 {
|
||||
t.Fatalf("availability hardening failures = %v", failures)
|
||||
}
|
||||
|
||||
unrelated := cloneContract(current)
|
||||
mutateTool(&unrelated, func(tool *toolSchema) { tool.Risk = "high" })
|
||||
normalized, err = normalizeSchemaCommandMigrations(baseline, unrelated, []interfacesnapshot.CommandMigration{migration})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if failures := strings.Join(checkCompatibility(normalized, unrelated), "\n"); !strings.Contains(failures, "changed risk") {
|
||||
t.Fatalf("unrelated risk drift was hidden: %q", failures)
|
||||
}
|
||||
|
||||
alreadyAfter := cloneContract(current)
|
||||
if _, err := normalizeSchemaCommandMigrations(alreadyAfter, current, []interfacesnapshot.CommandMigration{migration}); err != nil {
|
||||
t.Fatalf("consumed merge-base availability should remain inert: %v", err)
|
||||
}
|
||||
wrongPath := cloneContract(current)
|
||||
mutateTool(&wrongPath, func(tool *toolSchema) { tool.PrimaryCLIPath = "doc other" })
|
||||
normalized, err = normalizeSchemaCommandMigrations(baseline, wrongPath, []interfacesnapshot.CommandMigration{migration})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := normalized.Products["doc"].Tools["doc.create"].Availability; got != "available" {
|
||||
t.Fatalf("wrong-path migration changed availability to %q", got)
|
||||
}
|
||||
wrong := cloneContract(current)
|
||||
mutateTool(&wrong, func(tool *toolSchema) { tool.Availability = "available" })
|
||||
if _, err := normalizeSchemaCommandMigrations(baseline, wrong, []interfacesnapshot.CommandMigration{migration}); err == nil ||
|
||||
!strings.Contains(err.Error(), "does not match Schema availability") {
|
||||
t.Fatalf("wrong availability error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageSchemaCommandMigrationsAuthorizeOnlyExactProjection(t *testing.T) {
|
||||
baseline := schemaCommandMigrationContract(false)
|
||||
current := schemaCommandMigrationContract(true)
|
||||
|
||||
@@ -1869,6 +1869,24 @@ func normalizeSchemaCommandMigrations(
|
||||
normalizedProduct := normalized.Products[migration.Schema.ProductID]
|
||||
normalizedTool := normalizedProduct.Tools[migration.Schema.SourceToolID]
|
||||
switch migration.Kind {
|
||||
case interfacesnapshot.CommandMigrationAvailability:
|
||||
change := migration.Schema.Availability
|
||||
if change != nil && oldTool.Availability == change.After && newSource.Availability == change.After {
|
||||
continue
|
||||
}
|
||||
if change == nil || oldTool.Availability != change.Before || newSource.Availability != change.After {
|
||||
return schemaContract{}, fmt.Errorf(
|
||||
"approved availability hardening %q does not match Schema availability %q -> %q",
|
||||
migration.Legacy.Command,
|
||||
oldTool.Availability,
|
||||
newSource.Availability,
|
||||
)
|
||||
}
|
||||
if newSource.PrimaryCLIPath != legacyPath {
|
||||
continue
|
||||
}
|
||||
normalizedTool.Availability = newSource.Availability
|
||||
|
||||
case interfacesnapshot.CommandMigrationMove:
|
||||
if newSource.PrimaryCLIPath != replacementPath {
|
||||
continue
|
||||
|
||||
@@ -1106,6 +1106,9 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) {
|
||||
t.Fatal("Code Admission workflow missing Policy job boundaries")
|
||||
}
|
||||
policyJob := admission[policyStart:policyEnd]
|
||||
if !strings.Contains(policyJob, "timeout-minutes: 15") {
|
||||
t.Error("Policy job must retain enough headroom for full Schema policy validation")
|
||||
}
|
||||
requirePolicyEnv := func(step, nextStep string) {
|
||||
t.Helper()
|
||||
start := strings.Index(policyJob, " - name: "+step+"\n")
|
||||
|
||||
Reference in New Issue
Block a user