Skip to content

test: add NotFound/helm-delete path tests for DataProcess status han…#6068

Open
adityaupasani2 wants to merge 2 commits into
fluid-cloudnative:masterfrom
adityaupasani2:test/dataprocess-notfound-paths
Open

test: add NotFound/helm-delete path tests for DataProcess status han…#6068
adityaupasani2 wants to merge 2 commits into
fluid-cloudnative:masterfrom
adityaupasani2:test/dataprocess-notfound-paths

Conversation

@adityaupasani2

Copy link
Copy Markdown
Contributor

Ⅰ. Describe what this PR does

Adds unit tests covering the job/CronJob NotFound code path in all three DataProcess status handlers — the path that was flagged as untested in the PR #5969 review.

When the job or CronJob is not found, each handler deletes the helm release and returns early with the unchanged status and no error. Previously this path had zero coverage.

Uses gomonkey.ApplyFunc to patch helm.DeleteReleaseIfExists so the tests do not require the ddc-helm binary (which is not available in unit test environments — the same approach used for compatibility.IsBatchV1CronJobSupported in the existing Cron tests).

Ⅱ. Does this pull request fix one issue?

Closes #6066

Ⅲ. List the added test cases

  • TestOnceGetOperationStatusJobNotFound — Job not found → helm release deleted, unchanged status returned, no error
  • TestOnEventGetOperationStatusJobNotFound — same for OnEventStatusHandler
  • TestCronGetOperationStatusCronJobNotFound — CronJob not found → helm release deleted, unchanged status returned, no error

Ⅳ. Describe how to verify it

Note: pkg/controllers/v1alpha1/dataprocess has a pre-existing BeforeSuite Ginkgo decorator failure in suite_test.go that prevents the test runner from executing any tests in this package locally. This is present on upstream master too and is unrelated to this PR. The tests compile cleanly (go build, go vet pass).

Ⅴ. Special notes for reviews

This was promised as a follow-up to PR #5969 (review comment by cheyang).

…dlers

Add unit tests covering the job/CronJob NotFound code path in all three
DataProcess status handlers:

- TestOnceGetOperationStatusJobNotFound: Job not found triggers helm
  release deletion and returns unchanged status (no error)
- TestOnEventGetOperationStatusJobNotFound: same for OnEventStatusHandler
- TestCronGetOperationStatusCronJobNotFound: CronJob not found triggers
  helm release deletion and returns unchanged status

Uses gomonkey.ApplyFunc to patch helm.DeleteReleaseIfExists so the tests
do not require the ddc-helm binary (which is not available in unit test
environments).

This was promised as a follow-up to PR fluid-cloudnative#5969 (review comment by cheyang).

Closes fluid-cloudnative#6066

Signed-off-by: Aditya Upasani <adityaupasani29@gmail.com>
@fluid-e2e-bot

fluid-e2e-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign cheyang for approval by writing /assign @cheyang in a comment. For more information see:The Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@fluid-e2e-bot

fluid-e2e-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

Hi @adityaupasani2. Thanks for your PR.

I'm waiting for a fluid-cloudnative member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds unit tests to verify the behavior of the status handlers when a job or cronjob is not found. The review feedback highlights that the tests mock helm.DeleteReleaseIfExists but do not assert that it was actually called with the correct release name and namespace. Additionally, in TestCronGetOperationStatusCronJobNotFound, the context ctx is initialized without a namespace, which would cause the deletion to be requested in an empty namespace instead of "default". Code suggestions are provided to add these assertions and fix the context initialization.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +430 to +473
func TestOnceGetOperationStatusJobNotFound(t *testing.T) {
testScheme := runtime.NewScheme()
_ = v1alpha1.AddToScheme(testScheme)
_ = batchv1.AddToScheme(testScheme)

// Patch helm.DeleteReleaseIfExists to avoid shelling out to ddc-helm binary
helmPatch := gomonkey.ApplyFunc(helm.DeleteReleaseIfExists, func(name, namespace string) error {
return nil
})
defer helmPatch.Reset()

mockDataProcess := v1alpha1.DataProcess{
ObjectMeta: v1.ObjectMeta{
Name: "test",
Namespace: "default",
},
Spec: v1alpha1.DataProcessSpec{
Policy: v1alpha1.Once,
},
Status: v1alpha1.OperationStatus{
Phase: common.PhasePending,
},
}

// No job in fake client - simulates NotFound
client := fake.NewFakeClientWithScheme(testScheme, &mockDataProcess)
handler := &OnceStatusHandler{Client: client, dataProcess: &mockDataProcess}
ctx := cruntime.ReconcileRequestContext{
NamespacedName: types.NamespacedName{Namespace: "default", Name: ""},
Log: fake.NullLogger(),
}

// When job is not found, helm release is deleted and we get early return with unchanged status
opStatus, err := handler.GetOperationStatus(ctx, &mockDataProcess.Status)
if err != nil {
t.Errorf("unexpected error on NotFound path: %v", err)
}
if opStatus == nil {
t.Error("expected non-nil opStatus")
}
if opStatus.Phase != common.PhasePending {
t.Errorf("expected phase %s, got %s", common.PhasePending, opStatus.Phase)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The unit test mocks helm.DeleteReleaseIfExists but does not assert that it was actually called, nor does it verify that the correct release name and namespace were passed. Adding assertions to verify the mock call ensures that the helm release deletion path is actually executed and prevents false positives (e.g., if the deletion call is accidentally removed or bypassed in the handler).

func TestOnceGetOperationStatusJobNotFound(t *testing.T) {
	testScheme := runtime.NewScheme()
	_ = v1alpha1.AddToScheme(testScheme)
	_ = batchv1.AddToScheme(testScheme)

	// Patch helm.DeleteReleaseIfExists to avoid shelling out to ddc-helm binary
	var called bool
	var calledName, calledNamespace string
	helmPatch := gomonkey.ApplyFunc(helm.DeleteReleaseIfExists, func(name, namespace string) error {
		called = true
		calledName = name
		calledNamespace = namespace
		return nil
	})
	defer helmPatch.Reset()

	mockDataProcess := v1alpha1.DataProcess{
		ObjectMeta: v1.ObjectMeta{
			Name:      "test",
			Namespace: "default",
		},
		Spec: v1alpha1.DataProcessSpec{
			Policy: v1alpha1.Once,
		},
		Status: v1alpha1.OperationStatus{
			Phase: common.PhasePending,
		},
	}

	// No job in fake client - simulates NotFound
	client := fake.NewFakeClientWithScheme(testScheme, &mockDataProcess)
	handler := &OnceStatusHandler{Client: client, dataProcess: &mockDataProcess}
	ctx := cruntime.ReconcileRequestContext{
		NamespacedName: types.NamespacedName{Namespace: "default", Name: ""},
		Log:            fake.NullLogger(),
	}

	// When job is not found, helm release is deleted and we get early return with unchanged status
	opStatus, err := handler.GetOperationStatus(ctx, &mockDataProcess.Status)
	if err != nil {
		t.Errorf("unexpected error on NotFound path: %v", err)
	}
	if opStatus == nil {
		t.Error("expected non-nil opStatus")
	}
	if opStatus.Phase != common.PhasePending {
		t.Errorf("expected phase %s, got %s", common.PhasePending, opStatus.Phase)
	}
	if !called {
		t.Error("expected helm.DeleteReleaseIfExists to be called")
	}
	expectedReleaseName := utils.GetDataProcessReleaseName(mockDataProcess.GetName())
	if calledName != expectedReleaseName || calledNamespace != "default" {
		t.Errorf("expected helm.DeleteReleaseIfExists to be called with (%s, %s), got (%s, %s)", expectedReleaseName, "default", calledName, calledNamespace)
	}
}

Comment on lines +475 to +518
func TestOnEventGetOperationStatusJobNotFound(t *testing.T) {
testScheme := runtime.NewScheme()
_ = v1alpha1.AddToScheme(testScheme)
_ = batchv1.AddToScheme(testScheme)

// Patch helm.DeleteReleaseIfExists to avoid shelling out to ddc-helm binary
helmPatch := gomonkey.ApplyFunc(helm.DeleteReleaseIfExists, func(name, namespace string) error {
return nil
})
defer helmPatch.Reset()

mockDataProcess := v1alpha1.DataProcess{
ObjectMeta: v1.ObjectMeta{
Name: "test",
Namespace: "default",
},
Spec: v1alpha1.DataProcessSpec{
Policy: v1alpha1.OnEvent,
},
Status: v1alpha1.OperationStatus{
Phase: common.PhasePending,
},
}

// No job in fake client - simulates NotFound
client := fake.NewFakeClientWithScheme(testScheme, &mockDataProcess)
handler := &OnEventStatusHandler{Client: client, dataProcess: &mockDataProcess}
ctx := cruntime.ReconcileRequestContext{
NamespacedName: types.NamespacedName{Namespace: "default", Name: ""},
Log: fake.NullLogger(),
}

// When job is not found, helm release is deleted and we get early return with unchanged status
opStatus, err := handler.GetOperationStatus(ctx, &mockDataProcess.Status)
if err != nil {
t.Errorf("unexpected error on NotFound path: %v", err)
}
if opStatus == nil {
t.Error("expected non-nil opStatus")
}
if opStatus.Phase != common.PhasePending {
t.Errorf("expected phase %s, got %s", common.PhasePending, opStatus.Phase)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The unit test mocks helm.DeleteReleaseIfExists but does not assert that it was actually called, nor does it verify that the correct release name and namespace were passed. Adding assertions to verify the mock call ensures that the helm release deletion path is actually executed and prevents false positives.

func TestOnEventGetOperationStatusJobNotFound(t *testing.T) {
	testScheme := runtime.NewScheme()
	_ = v1alpha1.AddToScheme(testScheme)
	_ = batchv1.AddToScheme(testScheme)

	// Patch helm.DeleteReleaseIfExists to avoid shelling out to ddc-helm binary
	var called bool
	var calledName, calledNamespace string
	helmPatch := gomonkey.ApplyFunc(helm.DeleteReleaseIfExists, func(name, namespace string) error {
		called = true
		calledName = name
		calledNamespace = namespace
		return nil
	})
	defer helmPatch.Reset()

	mockDataProcess := v1alpha1.DataProcess{
		ObjectMeta: v1.ObjectMeta{
			Name:      "test",
			Namespace: "default",
		},
		Spec: v1alpha1.DataProcessSpec{
			Policy: v1alpha1.OnEvent,
		},
		Status: v1alpha1.OperationStatus{
			Phase: common.PhasePending,
		},
	}

	// No job in fake client - simulates NotFound
	client := fake.NewFakeClientWithScheme(testScheme, &mockDataProcess)
	handler := &OnEventStatusHandler{Client: client, dataProcess: &mockDataProcess}
	ctx := cruntime.ReconcileRequestContext{
		NamespacedName: types.NamespacedName{Namespace: "default", Name: ""},
		Log:            fake.NullLogger(),
	}

	// When job is not found, helm release is deleted and we get early return with unchanged status
	opStatus, err := handler.GetOperationStatus(ctx, &mockDataProcess.Status)
	if err != nil {
		t.Errorf("unexpected error on NotFound path: %v", err)
	}
	if opStatus == nil {
		t.Error("expected non-nil opStatus")
	}
	if opStatus.Phase != common.PhasePending {
		t.Errorf("expected phase %s, got %s", common.PhasePending, opStatus.Phase)
	}
	if !called {
		t.Error("expected helm.DeleteReleaseIfExists to be called")
	}
	expectedReleaseName := utils.GetDataProcessReleaseName(mockDataProcess.GetName())
	if calledName != expectedReleaseName || calledNamespace != "default" {
		t.Errorf("expected helm.DeleteReleaseIfExists to be called with (%s, %s), got (%s, %s)", expectedReleaseName, "default", calledName, calledNamespace)
	}
}

Comment on lines +520 to +566
func TestCronGetOperationStatusCronJobNotFound(t *testing.T) {
testScheme := runtime.NewScheme()
_ = v1alpha1.AddToScheme(testScheme)
_ = batchv1.AddToScheme(testScheme)

patch := gomonkey.ApplyFunc(compatibility.IsBatchV1CronJobSupported, func() bool {
return true
})
defer patch.Reset()

// Patch helm.DeleteReleaseIfExists to avoid shelling out to ddc-helm binary
helmPatch := gomonkey.ApplyFunc(helm.DeleteReleaseIfExists, func(name, namespace string) error {
return nil
})
defer helmPatch.Reset()

mockDataProcess := v1alpha1.DataProcess{
ObjectMeta: v1.ObjectMeta{
Name: "test",
Namespace: "default",
},
Spec: v1alpha1.DataProcessSpec{
Policy: v1alpha1.Cron,
Schedule: "* * * * *",
},
Status: v1alpha1.OperationStatus{
Phase: common.PhasePending,
},
}

// No CronJob in fake client - simulates NotFound
client := fake.NewFakeClientWithScheme(testScheme, &mockDataProcess)
handler := &CronStatusHandler{Client: client, dataProcess: &mockDataProcess}
ctx := cruntime.ReconcileRequestContext{Log: fake.NullLogger()}

// When CronJob is not found, helm release is deleted and we get early return with unchanged status
opStatus, err := handler.GetOperationStatus(ctx, &mockDataProcess.Status)
if err != nil {
t.Errorf("unexpected error on NotFound path: %v", err)
}
if opStatus == nil {
t.Error("expected non-nil opStatus")
}
if opStatus.Phase != common.PhasePending {
t.Errorf("expected phase %s, got %s", common.PhasePending, opStatus.Phase)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There are two issues here:

  1. The unit test mocks helm.DeleteReleaseIfExists but does not assert that it was actually called, nor does it verify that the correct release name and namespace were passed.
  2. The ctx context is initialized without a namespace (ctx := cruntime.ReconcileRequestContext{Log: fake.NullLogger()}). However, CronStatusHandler.GetOperationStatus uses ctx.Namespace when calling helm.DeleteReleaseIfExists(releaseName, ctx.Namespace). This means the deletion would be requested in an empty namespace "" instead of "default".

Initializing ctx with the correct namespace and adding assertions to verify the mock call makes the test robust and correct.

func TestCronGetOperationStatusCronJobNotFound(t *testing.T) {
	testScheme := runtime.NewScheme()
	_ = v1alpha1.AddToScheme(testScheme)
	_ = batchv1.AddToScheme(testScheme)

	patch := gomonkey.ApplyFunc(compatibility.IsBatchV1CronJobSupported, func() bool {
		return true
	})
	defer patch.Reset()

	// Patch helm.DeleteReleaseIfExists to avoid shelling out to ddc-helm binary
	var called bool
	var calledName, calledNamespace string
	helmPatch := gomonkey.ApplyFunc(helm.DeleteReleaseIfExists, func(name, namespace string) error {
		called = true
		calledName = name
		calledNamespace = namespace
		return nil
	})
	defer helmPatch.Reset()

	mockDataProcess := v1alpha1.DataProcess{
		ObjectMeta: v1.ObjectMeta{
			Name:      "test",
			Namespace: "default",
		},
		Spec: v1alpha1.DataProcessSpec{
			Policy:   v1alpha1.Cron,
			Schedule: "* * * * *",
		},
		Status: v1alpha1.OperationStatus{
			Phase: common.PhasePending,
		},
	}

	// No CronJob in fake client - simulates NotFound
	client := fake.NewFakeClientWithScheme(testScheme, &mockDataProcess)
	handler := &CronStatusHandler{Client: client, dataProcess: &mockDataProcess}
	ctx := cruntime.ReconcileRequestContext{
		NamespacedName: types.NamespacedName{Namespace: "default", Name: ""},
		Log:            fake.NullLogger(),
	}

	// When CronJob is not found, helm release is deleted and we get early return with unchanged status
	opStatus, err := handler.GetOperationStatus(ctx, &mockDataProcess.Status)
	if err != nil {
		t.Errorf("unexpected error on NotFound path: %v", err)
	}
	if opStatus == nil {
		t.Error("expected non-nil opStatus")
	}
	if opStatus.Phase != common.PhasePending {
		t.Errorf("expected phase %s, got %s", common.PhasePending, opStatus.Phase)
	}
	if !called {
		t.Error("expected helm.DeleteReleaseIfExists to be called")
	}
	expectedReleaseName := utils.GetDataProcessReleaseName(mockDataProcess.GetName())
	if calledName != expectedReleaseName || calledNamespace != "default" {
		t.Errorf("expected helm.DeleteReleaseIfExists to be called with (%s, %s), got (%s, %s)", expectedReleaseName, "default", calledName, calledNamespace)
	}
}

@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 64.77%. Comparing base (36f0467) to head (a10d42b).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #6068   +/-   ##
=======================================
  Coverage   64.77%   64.77%           
=======================================
  Files         484      484           
  Lines       33892    33892           
=======================================
  Hits        21954    21954           
  Misses      10215    10215           
  Partials     1723     1723           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

if opStatus == nil {
t.Error("expected non-nil opStatus")
}
if opStatus.Phase != common.PhasePending {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lint and staticcheck jobs are red on this PR because of SA5011 here (also at lines 515 and 563). After if opStatus == nil { t.Error(...) } you fall through and immediately read opStatus.Phase without return, so staticcheck flags this as a possible nil pointer dereference. The pattern needs to either short-circuit (t.Fatal(...) / return) or guard the phase check inside an else. PR is currently BLOCKED on Project Check because of these three findings.

if opStatus == nil {
t.Error("expected non-nil opStatus")
}
if opStatus.Phase != common.PhasePending {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same SA5011 pattern as the Once case: line 512 checks opStatus == nil and continues, then line 515 dereferences opStatus.Phase. This is the second of three lint failures gating the PR.

if opStatus == nil {
t.Error("expected non-nil opStatus")
}
if opStatus.Phase != common.PhasePending {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same pattern, third occurrence: nil check at 560 does not return/t.Fatal, so 563 still dereferences opStatus.Phase. Once all three are fixed (e.g., switch to t.Fatal for the nil branch), staticcheck and lint should turn green and merge can unblock.

// No CronJob in fake client - simulates NotFound
client := fake.NewFakeClientWithScheme(testScheme, &mockDataProcess)
handler := &CronStatusHandler{Client: client, dataProcess: &mockDataProcess}
ctx := cruntime.ReconcileRequestContext{Log: fake.NullLogger()}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ctx here is built without a Namespace, but in production CronStatusHandler.GetOperationStatus passes ctx.Namespace to helm.DeleteReleaseIfExists (status_handler.go:113). With your no-op patch the test still passes, but it would also pass if the production code silently called DeleteReleaseIfExists(releaseName, ""). Setting NamespacedName: types.NamespacedName{Namespace: "default"} (as the Once/OnEvent tests do) makes the test reflect the real call signature and matches the Gemini bot's observation.

_ = batchv1.AddToScheme(testScheme)

// Patch helm.DeleteReleaseIfExists to avoid shelling out to ddc-helm binary
helmPatch := gomonkey.ApplyFunc(helm.DeleteReleaseIfExists, func(name, namespace string) error {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole point of these tests is to prove the NotFound branch reaches helm.DeleteReleaseIfExists. Right now the patched stub just returns nil and is never checked, so a future refactor that removes the delete call entirely would still keep the test green. Capturing the call (e.g., flip a local called bool or record name/namespace and assert them after GetOperationStatus returns) would lock in the contract this PR is meant to cover. The Gemini review flagged the same gap.

…ests

- Replace t.Error with t.Fatal for nil opStatus checks to prevent
  nil pointer dereference (SA5011 staticcheck finding that was
  blocking lint/staticcheck CI)
- Add called bool + name/namespace capture to verify helm.DeleteReleaseIfExists
  is actually invoked with the correct release name and namespace,
  so a future removal of the delete call would fail the test
- Fix CronStatusHandler test: set ctx.Namespace to "default" so it
  matches the namespace passed to helm.DeleteReleaseIfExists in
  production code (ctx.Namespace), making the assertion meaningful

Addresses cheyang and Gemini review comments on fluid-cloudnative#6068

Signed-off-by: Aditya Upasani <adityaupasani29@gmail.com>
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Test] Add unit tests for NotFound/helm-delete path in DataProcess CronStatusHandler and OnEventStatusHandler

2 participants