@@ -45,6 +45,11 @@ func TestAppsGitCredentialInitDryRunRequestShape(t *testing.T) {
4545 Params map [string ]interface {} `json:"params"`
4646 Body interface {} `json:"body"`
4747 } `json:"api"`
48+ Mode string `json:"mode"`
49+ Action string `json:"action"`
50+ AppID string `json:"app_id"`
51+ MetadataFile string `json:"metadata_file"`
52+ LocalEffects []string `json:"local_effects"`
4853 }
4954 if err := json .Unmarshal ([]byte (stdout .String ()), & payload ); err != nil {
5055 t .Fatalf ("decode dry-run output: %v\n %s" , err , stdout .String ())
@@ -65,6 +70,107 @@ func TestAppsGitCredentialInitDryRunRequestShape(t *testing.T) {
6570 if call .Body != nil {
6671 t .Fatalf ("body = %#v, want nil" , call .Body )
6772 }
73+ if payload .Mode != "api-plus-local-setup" {
74+ t .Fatalf ("mode = %q" , payload .Mode )
75+ }
76+ if payload .Action != "initialize_local_git_credential" {
77+ t .Fatalf ("action = %q" , payload .Action )
78+ }
79+ if payload .AppID != "app_xxx" {
80+ t .Fatalf ("app_id = %q" , payload .AppID )
81+ }
82+ if ! strings .HasSuffix (payload .MetadataFile , filepath .Join ("spark" , "app_xxx" , "git.json" )) {
83+ t .Fatalf ("metadata_file = %q" , payload .MetadataFile )
84+ }
85+ assertStringSliceEqual (t , payload .LocalEffects , []string {
86+ "save the issued PAT in the local system credential store" ,
87+ "write app-scoped git credential metadata" ,
88+ "configure a URL-scoped Git credential helper in global git config when possible" ,
89+ })
90+ }
91+
92+ func TestAppsGitCredentialListDryRunDescribesLocalReads (t * testing.T ) {
93+ factory , stdout , _ := newAppsExecuteFactory (t )
94+ if err := runAppsShortcut (t , AppsGitCredentialList ,
95+ []string {"+git-credential-list" , "--dry-run" , "--as" , "user" },
96+ factory , stdout ); err != nil {
97+ t .Fatalf ("dry-run err=%v" , err )
98+ }
99+ var payload struct {
100+ Description string `json:"description"`
101+ API []interface {} `json:"api"`
102+ Mode string `json:"mode"`
103+ Action string `json:"action"`
104+ StorageRoot string `json:"storage_root"`
105+ Reads []string `json:"reads"`
106+ }
107+ if err := json .Unmarshal ([]byte (stdout .String ()), & payload ); err != nil {
108+ t .Fatalf ("decode dry-run output: %v\n %s" , err , stdout .String ())
109+ }
110+ if payload .Description != "Preview local Git credential listing (no API call, read-only local state)." {
111+ t .Fatalf ("description = %q" , payload .Description )
112+ }
113+ if len (payload .API ) != 0 {
114+ t .Fatalf ("api len = %d, want 0" , len (payload .API ))
115+ }
116+ if payload .Mode != "local-read-only" {
117+ t .Fatalf ("mode = %q" , payload .Mode )
118+ }
119+ if payload .Action != "list_local_git_credentials" {
120+ t .Fatalf ("action = %q" , payload .Action )
121+ }
122+ if ! strings .HasSuffix (payload .StorageRoot , filepath .Join ("spark" )) {
123+ t .Fatalf ("storage_root = %q" , payload .StorageRoot )
124+ }
125+ assertStringSliceEqual (t , payload .Reads , []string {
126+ "scan app-scoped git credential metadata under the CLI config directory" ,
127+ "derive per-app repository URLs and local credential status from local metadata" ,
128+ })
129+ }
130+
131+ func TestAppsGitCredentialRemoveDryRunDescribesLocalCleanup (t * testing.T ) {
132+ factory , stdout , _ := newAppsExecuteFactory (t )
133+ if err := runAppsShortcut (t , AppsGitCredentialRemove ,
134+ []string {"+git-credential-remove" , "--app-id" , "app_xxx" , "--dry-run" , "--as" , "user" },
135+ factory , stdout ); err != nil {
136+ t .Fatalf ("dry-run err=%v" , err )
137+ }
138+ var payload struct {
139+ Description string `json:"description"`
140+ API []interface {} `json:"api"`
141+ Mode string `json:"mode"`
142+ Action string `json:"action"`
143+ AppID string `json:"app_id"`
144+ MetadataFile string `json:"metadata_file"`
145+ Effects []string `json:"effects"`
146+ }
147+ if err := json .Unmarshal ([]byte (stdout .String ()), & payload ); err != nil {
148+ t .Fatalf ("decode dry-run output: %v\n %s" , err , stdout .String ())
149+ }
150+ if payload .Description != "Preview local Git credential cleanup (no API call; would clean up local-only state)." {
151+ t .Fatalf ("description = %q" , payload .Description )
152+ }
153+ if len (payload .API ) != 0 {
154+ t .Fatalf ("api len = %d, want 0" , len (payload .API ))
155+ }
156+ if payload .Mode != "local-cleanup-only" {
157+ t .Fatalf ("mode = %q" , payload .Mode )
158+ }
159+ if payload .Action != "remove_local_git_credential" {
160+ t .Fatalf ("action = %q" , payload .Action )
161+ }
162+ if payload .AppID != "app_xxx" {
163+ t .Fatalf ("app_id = %q" , payload .AppID )
164+ }
165+ if ! strings .HasSuffix (payload .MetadataFile , filepath .Join ("spark" , "app_xxx" , "git.json" )) {
166+ t .Fatalf ("metadata_file = %q" , payload .MetadataFile )
167+ }
168+ assertStringSliceEqual (t , payload .Effects , []string {
169+ "read app-scoped git credential metadata" ,
170+ "remove the saved PAT from the local system credential store" ,
171+ "remove the app-scoped Git helper from global git config when present" ,
172+ "delete the local metadata record after cleanup succeeds" ,
173+ })
68174}
69175
70176func TestAppsGitCredentialInitRequiresAppID (t * testing.T ) {
@@ -579,6 +685,18 @@ func TestAppsGitCredentialRemoveReturnsStoreError(t *testing.T) {
579685 }
580686}
581687
688+ func assertStringSliceEqual (t * testing.T , got , want []string ) {
689+ t .Helper ()
690+ if len (got ) != len (want ) {
691+ t .Fatalf ("slice len = %d, want %d; got %#v" , len (got ), len (want ), got )
692+ }
693+ for i := range want {
694+ if got [i ] != want [i ] {
695+ t .Fatalf ("slice[%d] = %q, want %q; got %#v" , i , got [i ], want [i ], got )
696+ }
697+ }
698+ }
699+
582700func TestGitCredentialLocalErrorWrapsOnlyPlainErrors (t * testing.T ) {
583701 plain := errors .New ("git config failed" )
584702 wrapped := gitCredentialLocalError ("List local Miaoda Git credentials" , plain )
0 commit comments