@@ -2343,3 +2343,128 @@ func TestGenerate_ChildNameLimits(t *testing.T) {
23432343 })
23442344 }
23452345}
2346+
2347+ // TestBuildApplicationData_ApplyOutOfSyncOnly is the regression-proofing
2348+ // test for the SCOPING of ApplicationData.ApplyOutOfSyncOnly, not just its
2349+ // presence: it exercises all four folder kinds for the same parent
2350+ // component so a test that only checked the readiness case could not pass
2351+ // if the predicate degenerated to unconditionally true. Paired with the
2352+ // Job-level Replace=true,Force=true annotation in
2353+ // pkg/bundler/gatemanifest/manifest.go, ApplyOutOfSyncOnly (set only for
2354+ // the readiness Application) stops ArgoCD from delete-and-recreating the
2355+ // readiness-gate Job on every no-op resync. See #2367.
2356+ func TestBuildApplicationData_ApplyOutOfSyncOnly (t * testing.T ) {
2357+ comp := recipe.ComponentRef {
2358+ Name : "gpu-operator" ,
2359+ Source : "https://helm.ngc.nvidia.com/nvidia" ,
2360+ Chart : "gpu-operator" ,
2361+ Version : "v25.3.3" ,
2362+ }
2363+
2364+ tests := []struct {
2365+ name string
2366+ folder localformat.Folder
2367+ want bool
2368+ }{
2369+ {
2370+ name : "primary folder" ,
2371+ folder : localformat.Folder {Name : "gpu-operator" , Dir : "001-gpu-operator" , Kind : localformat .KindUpstreamHelm , Parent : "gpu-operator" },
2372+ want : false ,
2373+ },
2374+ {
2375+ name : "-pre folder" ,
2376+ folder : localformat.Folder {Name : "gpu-operator-pre" , Dir : "001-gpu-operator-pre" , Kind : localformat .KindLocalHelm , Parent : "gpu-operator" },
2377+ want : false ,
2378+ },
2379+ {
2380+ name : "-post folder" ,
2381+ folder : localformat.Folder {Name : "gpu-operator-post" , Dir : "003-gpu-operator-post" , Kind : localformat .KindLocalHelm , Parent : "gpu-operator" },
2382+ want : false ,
2383+ },
2384+ {
2385+ name : "-readiness folder" ,
2386+ folder : localformat.Folder {Name : "gpu-operator-readiness" , Dir : "004-gpu-operator-readiness" , Kind : localformat .KindLocalHelm , Parent : "gpu-operator" },
2387+ want : true ,
2388+ },
2389+ {
2390+ // Adversarial: a primary folder whose NAME merely contains
2391+ // "-readiness" (not the synthetic suffix pattern relative to
2392+ // its own Parent) must not be scoped in. f.Parent+"-readiness"
2393+ // = "foo-readiness-readiness" != "foo-readiness", so the
2394+ // predicate correctly evaluates false.
2395+ name : "primary folder whose name happens to contain -readiness" ,
2396+ folder : localformat.Folder {Name : "foo-readiness" , Dir : "005-foo-readiness" , Kind : localformat .KindUpstreamHelm , Parent : "foo-readiness" },
2397+ want : false ,
2398+ },
2399+ }
2400+
2401+ for _ , tt := range tests {
2402+ t .Run (tt .name , func (t * testing.T ) {
2403+ data , err := buildApplicationData (comp , tt .folder , 0 , "https://github.com/example/repo.git" , "main" , nil , false )
2404+ if err != nil {
2405+ t .Fatalf ("buildApplicationData() error = %v" , err )
2406+ }
2407+ if data .ApplyOutOfSyncOnly != tt .want {
2408+ t .Errorf ("ApplyOutOfSyncOnly = %v, want %v (folder=%+v)" , data .ApplyOutOfSyncOnly , tt .want , tt .folder )
2409+ }
2410+ })
2411+ }
2412+ }
2413+
2414+ // TestGenerate_ApplyOutOfSyncOnlySyncOptions asserts the rendered
2415+ // application.yaml for a -readiness folder carries the exact
2416+ // "- ApplyOutOfSyncOnly=true" syncOptions entry, and that a non-readiness
2417+ // folder's rendered application.yaml does not mention ApplyOutOfSyncOnly
2418+ // anywhere. See #2367.
2419+ func TestGenerate_ApplyOutOfSyncOnlySyncOptions (t * testing.T ) {
2420+ ctx := context .Background ()
2421+ outputDir := t .TempDir ()
2422+
2423+ recipeResult := & recipe.RecipeResult {}
2424+ recipeResult .Metadata .Version = testVersion
2425+ recipeResult .ComponentRefs = []recipe.ComponentRef {
2426+ {
2427+ Name : "gpu-operator" ,
2428+ Namespace : "gpu-operator" ,
2429+ Chart : "gpu-operator" ,
2430+ Version : "v25.3.3" ,
2431+ Type : recipe .ComponentTypeHelm ,
2432+ Source : "https://helm.ngc.nvidia.com/nvidia" ,
2433+ },
2434+ }
2435+ recipeResult .DeploymentOrder = []string {"gpu-operator" }
2436+
2437+ g := & Generator {
2438+ RecipeResult : recipeResult ,
2439+ ComponentValues : map [string ]map [string ]any {"gpu-operator" : {}},
2440+ Version : "v0.0.0-test" ,
2441+ RepoURL : "https://github.com/example/aicr-bundles.git" ,
2442+ TargetRevision : "main" ,
2443+ ComponentReadiness : map [string ]map [string ][]byte {
2444+ "gpu-operator" : {
2445+ "readiness.yaml" : []byte ("apiVersion: batch/v1\n kind: Job\n metadata:\n " +
2446+ " name: gpu-operator-readiness-gate\n namespace: {{ .Release.Namespace }}\n " ),
2447+ },
2448+ },
2449+ }
2450+
2451+ if _ , err := g .Generate (ctx , outputDir ); err != nil {
2452+ t .Fatalf ("Generate() error = %v" , err )
2453+ }
2454+
2455+ primary , err := os .ReadFile (filepath .Join (outputDir , "001-gpu-operator" , "application.yaml" ))
2456+ if err != nil {
2457+ t .Fatalf ("read primary application.yaml: %v" , err )
2458+ }
2459+ if strings .Contains (string (primary ), "ApplyOutOfSyncOnly" ) {
2460+ t .Errorf ("primary Application must not mention ApplyOutOfSyncOnly:\n %s" , primary )
2461+ }
2462+
2463+ readiness , err := os .ReadFile (filepath .Join (outputDir , "002-gpu-operator-readiness" , "application.yaml" ))
2464+ if err != nil {
2465+ t .Fatalf ("read readiness application.yaml: %v" , err )
2466+ }
2467+ if ! strings .Contains (string (readiness ), "- ApplyOutOfSyncOnly=true" ) {
2468+ t .Errorf ("readiness Application must contain \" - ApplyOutOfSyncOnly=true\" :\n %s" , readiness )
2469+ }
2470+ }
0 commit comments