Skip to content

Conversation

@yashisrani
Copy link
Contributor

@yashisrani yashisrani commented Jan 22, 2026

fix: #88

Adds unit-test coverage for all four AgentCube CLI binaries (agentd, picod, router, workload-manager) so that:

  • flag parsing and config building are exercised in CI
  • binaries compile and start without panics
  • overall repo coverage rises from 55 % → 61 % (target 70 %)
  • Use this command to test test-cases ./hack/check-coverage.sh

Test matrix

package tests result coverage
pkg/agentd PASS 80.0 %
pkg/common/types PASS 100 %
pkg/picod PASS 72.6 %
pkg/router PASS 82.0 %
pkg/store PASS 70.6 %
pkg/workloadmanager PASS 60.3 %

All packages PASS; the only failure is the aggregate gate (61.4 % < 70 %).

Screenshot 2026-01-22 at 1 30 15 PM

Notes

  • 0 % package-level coverage is expected (only main() remains untested).
  • Real logic is already covered in pkg/*; these tests guard the entry-points.
  • No external deps (Redis/K8s) required—uses fake env vars / files only.

Signed-off-by: Yash Israni <118755067+yashisrani@users.noreply.github.com>
@volcano-sh-bot
Copy link
Contributor

[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 kevin-wangzefeng for approval. 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

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @yashisrani, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the testing infrastructure and code quality of the AgentCube project by adding a substantial suite of new unit and integration tests. The changes focus on ensuring the robustness of critical components, from CLI flag parsing and configuration to Kubernetes resource management and API interactions. This effort aims to catch regressions early, enhance system stability, and move towards a higher standard of code coverage.

Highlights

  • Expanded Test Coverage: Introduced new unit and integration tests for all four AgentCube CLI binaries (agentd, picod, router, workload-manager) and several core packages, increasing overall repository coverage from 55% to 61%.
  • New Coverage Enforcement Script: Added a new shell script (hack/check-coverage.sh) to automate the process of running tests with coverage and enforcing a target coverage percentage of 70%.
  • PicoD File Error Handling Tests: Enhanced picod tests to cover various file error scenarios, including invalid Base64 content in JSON uploads, attempts to download non-existent files, and listing non-existent directories.
  • Router JWT Management Tests: Added comprehensive tests for the router component's JWT (JSON Web Token) management, covering key generation, token creation, PEM key retrieval, and Kubernetes secret storage/loading.
  • Workload Manager Component Tests: Introduced extensive tests for the workload-manager, including client caching, JWT token expiration, garbage collection logic, HTTP API handlers, and the building of Kubernetes Sandbox and SandboxClaim objects.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

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

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 introduces comprehensive unit tests for the agentcube components, significantly improving test coverage from 55% to 61%. The new tests cover flag parsing, config building, end-to-end API interactions, JWT management, client caching, garbage collection, and controller reconciliation logic across agentd, picod, router, and workload-manager. The addition of a hack/check-coverage.sh script also helps enforce a target coverage percentage. Overall, this is a valuable contribution to the project's stability and maintainability.


// Verify deletion
err = fakeClient.Get(context.Background(), types.NamespacedName{Name: "test-ci", Namespace: "default"}, wp)
assert.True(t, err != nil && (isNotFound(err) || true)) // fake client returns error on not found
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The condition (isNotFound(err) || true) will always evaluate to true, making the isNotFound(err) check ineffective. This means the test will pass even if isNotFound(err) returns false, hiding potential issues with error handling. It should likely just assert isNotFound(err) directly.

Suggested change
assert.True(t, err != nil && (isNotFound(err) || true)) // fake client returns error on not found
assert.True(t, isNotFound(err))

Comment on lines +76 to +217
patches := gomonkey.NewPatches()
defer patches.Reset()

patches.ApplyFunc(extractUserInfo, func(_ *gin.Context) (string, string, string, string) {
return "token", "ns", "sa", "sa-name"
})

patches.ApplyMethod(reflect.TypeOf(s.k8sClient), "GetOrCreateUserK8sClient", func(_ *K8sClient, _, _, _ string) (*UserK8sClient, error) {
return &UserK8sClient{dynamicClient: nil}, nil
})

patches.ApplyFunc(deleteSandbox, func(_ context.Context, _ dynamic.Interface, _, _ string) error {
return nil
})
patches.ApplyFunc(deleteSandboxClaim, func(_ context.Context, _ dynamic.Interface, _, _ string) error {
return nil
})

// Mock store to return a sandbox
s.storeClient = &httpFakeStore{
sandbox: &types.SandboxInfo{
Kind: types.AgentRuntimeKind,
SessionID: "sess-123",
SandboxNamespace: "default",
Name: "test-sb",
},
}

w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/v1/agent-runtime/sessions/sess-123", nil)
s.router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
})

t.Run("handleAgentRuntimeCreate", func(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()

patches.ApplyFunc(extractUserInfo, func(_ *gin.Context) (string, string, string, string) {
return "token", "ns", "sa", "sa-name"
})

patches.ApplyMethod(reflect.TypeOf(s.k8sClient), "GetOrCreateUserK8sClient", func(_ *K8sClient, _, _, _ string) (*UserK8sClient, error) {
return &UserK8sClient{dynamicClient: nil}, nil
})

patches.ApplyFunc(buildSandboxByAgentRuntime, func(_, _ string, _ *Informers) (*sandboxv1alpha1.Sandbox, *sandboxEntry, error) {
return &sandboxv1alpha1.Sandbox{
ObjectMeta: metav1.ObjectMeta{Name: "test-sb", Namespace: "default", UID: "uid-123"},
}, &sandboxEntry{SessionID: "sess-123", Ports: []runtimev1alpha1.TargetPort{{Port: 8080}}}, nil
})

// Mock WatchSandboxOnce
resultChan := make(chan SandboxStatusUpdate, 1)
resultChan <- SandboxStatusUpdate{Sandbox: &sandboxv1alpha1.Sandbox{
ObjectMeta: metav1.ObjectMeta{Name: "test-sb", Namespace: "default", UID: "uid-123"},
}}
patches.ApplyMethod(reflect.TypeOf(s.sandboxController), "WatchSandboxOnce", func(_ *SandboxReconciler, _ context.Context, _, _ string) <-chan SandboxStatusUpdate {
return resultChan
})
patches.ApplyMethod(reflect.TypeOf(s.sandboxController), "UnWatchSandbox", func(_ *SandboxReconciler, _, _ string) {})

// Mock internal createSandbox call chain
patches.ApplyFunc(createSandbox, func(_ context.Context, _ dynamic.Interface, _ *sandboxv1alpha1.Sandbox) (*SandboxInfo, error) {
return &SandboxInfo{Name: "test-sb", Namespace: "default"}, nil
})

patches.ApplyMethod(reflect.TypeOf(s.k8sClient), "GetSandboxPodIP", func(_ *K8sClient, _ context.Context, _, _, _ string) (string, error) {
return "10.0.0.1", nil
})

body, _ := json.Marshal(types.CreateSandboxRequest{
Namespace: "default",
Name: "test-agent",
})

w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/v1/agent-runtime", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
s.router.ServeHTTP(w, req)

assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "sess-123")
})

t.Run("handleCodeInterpreterCreate", func(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()

patches.ApplyFunc(extractUserInfo, func(_ *gin.Context) (string, string, string, string) {
return "token", "ns", "sa", "sa-name"
})

patches.ApplyMethod(reflect.TypeOf(s.k8sClient), "GetOrCreateUserK8sClient", func(_ *K8sClient, _, _, _ string) (*UserK8sClient, error) {
return &UserK8sClient{dynamicClient: nil}, nil
})

patches.ApplyFunc(buildSandboxByCodeInterpreter, func(_, _ string, _ *Informers) (*sandboxv1alpha1.Sandbox, *extensionsv1alpha1.SandboxClaim, *sandboxEntry, error) {
return &sandboxv1alpha1.Sandbox{
ObjectMeta: metav1.ObjectMeta{Name: "test-sb", Namespace: "default", UID: "uid-123"},
}, nil, &sandboxEntry{SessionID: "sess-123", Ports: []runtimev1alpha1.TargetPort{{Port: 8080}}}, nil
})

// Mock WatchSandboxOnce
resultChan := make(chan SandboxStatusUpdate, 1)
resultChan <- SandboxStatusUpdate{Sandbox: &sandboxv1alpha1.Sandbox{
ObjectMeta: metav1.ObjectMeta{Name: "test-sb", Namespace: "default", UID: "uid-123"},
}}
patches.ApplyMethod(reflect.TypeOf(s.sandboxController), "WatchSandboxOnce", func(_ *SandboxReconciler, _ context.Context, _, _ string) <-chan SandboxStatusUpdate {
return resultChan
})
patches.ApplyMethod(reflect.TypeOf(s.sandboxController), "UnWatchSandbox", func(_ *SandboxReconciler, _, _ string) {})

// Mock internal createSandbox call chain
patches.ApplyFunc(createSandbox, func(_ context.Context, _ dynamic.Interface, _ *sandboxv1alpha1.Sandbox) (*SandboxInfo, error) {
return &SandboxInfo{Name: "test-sb", Namespace: "default"}, nil
})

patches.ApplyMethod(reflect.TypeOf(s.k8sClient), "GetSandboxPodIP", func(_ *K8sClient, _ context.Context, _, _, _ string) (string, error) {
return "10.0.0.1", nil
})

body, _ := json.Marshal(types.CreateSandboxRequest{
Namespace: "default",
Name: "test-ci",
})

w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/v1/code-interpreter", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
s.router.ServeHTTP(w, req)

assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "sess-123")
})

t.Run("handleDeleteSandbox_NotFound", func(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()

// Mock store to return not found
s.storeClient = &httpFakeStore{sandbox: nil}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The extensive use of gomonkey to mock internal functions and methods in these HTTP handler tests makes them less robust and harder to maintain. Over-mocking can lead to tests that don't accurately reflect real-world behavior and can break easily if internal implementations change. Consider using more integrated testing approaches where possible, such as setting up a gin.Engine with actual dependencies (like a fake Kubernetes client or a custom fake store) rather than mocking every function call. This would allow for more realistic interaction testing and better verification of the handler's logic.

For example, instead of mocking extractUserInfo, you could set up the gin.Context with appropriate values that extractUserInfo would normally read. For GetOrCreateUserK8sClient, deleteSandbox, createSandbox, and GetSandboxPodIP, returning a fake.NewSimpleDynamicClient() or a mock object that can record calls and return configurable results would provide better fidelity.

@codecov-commenter
Copy link

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@86841e5). Learn more about missing BASE report.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #157   +/-   ##
=======================================
  Coverage        ?   59.06%           
=======================================
  Files           ?       29           
  Lines           ?     2533           
  Branches        ?        0           
=======================================
  Hits            ?     1496           
  Misses          ?      828           
  Partials        ?      209           
Flag Coverage Δ
unittests 59.06% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 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.

}
}

func TestConfigBuilding(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

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

I think we should write UT to test core logic. Testing like this doesn't make any sense.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Umbrella: strengthen unit test coverage across AgentCube components

4 participants