Repository: kubevela/kubevela

Issue:
Test: Enhance Unit Test Coverage for `pkg/addon`
#### Description

To improve the overall quality, stability, and maintainability of the KubeVela codebase, our goal is to increase unit test coverage. Good test coverage is essential for preventing regressions and enabling developers to refactor and add new features with confidence, particularly in critical areas like the addon system.

#### Task Description

This task involves adding comprehensive unit tests and refactoring existing tests for the `pkg/addon` package, which has several coverage gaps in its various reader implementations and helper utilities.

#### Affected Packages

*   `pkg/addon`

#### Key Contributions

This effort includes several key improvements:

*   **Refactor all addon reader tests** (`gitee`, `github`, `gitlab`, `local`, `memory`) to use consistent, modern testing patterns like sub-tests and table-driven tests.
*   **Add comprehensive unit tests** for the versioned addon registry logic, including `resolveAddonListFromIndex` and `loadAddon`.
*   **Add unit tests for previously uncovered helper functions** like `ClassifyItemByPattern`, `NewAsyncReader`, `WrapErrRateLimit`, and various others in `utils.go`.
*   **Uncover and fix two bugs** in the `reader_gitlab.go` implementation that were discovered during the testing process, improving the robustness of the GitLab addon provider.

#### Benefits

This work significantly:
*   Increases the reliability of the addon system by preventing regressions in how addons are read and handled.
*   Improves test maintainability by adopting modern testing patterns across the package.
*   Facilitates future development by providing a robust testing foundation for the addon engine.


Hints from issue comments:

Solution patch:
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 16c3e7aa..087d1d9d 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,35 +1,35 @@
 # This file is a github code protect rule follow the codeowners https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-code-owners#example-of-a-codeowners-file
 
-*                                  @barnettZQG @wonderflow @leejanee @Somefive @jefree-cat @FogDong @wangyikewxgm @chivalryq @anoop2811
-design/                            @barnettZQG @leejanee @wonderflow @Somefive @jefree-cat @FogDong @anoop2811
+*                                  @barnettZQG @wonderflow @leejanee @Somefive @jefree-cat @FogDong @wangyikewxgm @chivalryq @anoop2811 @briankane @jguionnet
+design/                            @barnettZQG @leejanee @wonderflow @Somefive @jefree-cat @FogDong @anoop2811 @briankane @jguionnet
 
 # Owner of Core Controllers
-pkg/controller/core.oam.dev       @Somefive @FogDong @barnettZQG @wonderflow @wangyikewxgm @chivalryq @anoop2811
+pkg/controller/core.oam.dev       @Somefive @FogDong @barnettZQG @wonderflow @wangyikewxgm @chivalryq @anoop2811 @briankane @jguionnet
 
 # Owner of Standard Controllers
-pkg/controller/standard.oam.dev   @wangyikewxgm @barnettZQG @wonderflow @Somefive @anoop2811 @FogDong
+pkg/controller/standard.oam.dev   @wangyikewxgm @barnettZQG @wonderflow @Somefive @anoop2811 @FogDong @briankane @jguionnet
 
 # Owner of CUE
-pkg/cue                            @leejanee @FogDong @Somefive @anoop2811
-pkg/stdlib                         @leejanee @FogDong @Somefive @anoop2811
+pkg/cue                            @leejanee @FogDong @Somefive @anoop2811 @briankane @jguionnet
+pkg/stdlib                         @leejanee @FogDong @Somefive @anoop2811 @briankane @jguionnet
 
 # Owner of Workflow
-pkg/workflow                       @leejanee @FogDong @Somefive @wangyikewxgm @chivalryq @anoop2811
+pkg/workflow                       @leejanee @FogDong @Somefive @wangyikewxgm @chivalryq @anoop2811 @briankane @jguionnet
 
 # Owner of vela templates
-vela-templates/                     @Somefive @barnettZQG @wonderflow @FogDong @wangyikewxgm @chivalryq @anoop2811
+vela-templates/                     @Somefive @barnettZQG @wonderflow @FogDong @wangyikewxgm @chivalryq @anoop2811 @briankane @jguionnet
 
 # Owner of vela CLI
-references/cli/                     @Somefive @StevenLeiZhang @charlie0129 @wangyikewxgm @chivalryq @anoop2811 @FogDong
+references/cli/                     @Somefive @StevenLeiZhang @charlie0129 @wangyikewxgm @chivalryq @anoop2811 @FogDong @briankane @jguionnet
 
 # Owner of vela addon framework
-pkg/addon/                         @wangyikewxgm @wonderflow @charlie0129 @anoop2811 @FogDong
+pkg/addon/                         @wangyikewxgm @wonderflow @charlie0129 @anoop2811 @FogDong @briankane @jguionnet
 
 # Owner of resource keeper and tracker
-pkg/resourcekeeper                 @Somefive @FogDong @chivalryq @anoop2811
-pkg/resourcetracker                @Somefive @FogDong @chivalryq @anoop2811
+pkg/resourcekeeper                 @Somefive @FogDong @chivalryq @anoop2811 @briankane @jguionnet
+pkg/resourcetracker                @Somefive @FogDong @chivalryq @anoop2811 @briankane @jguionnet
 
-.github/                           @chivalryq @wonderflow @Somefive @FogDong @wangyikewxgm @anoop2811
-makefiles                          @chivalryq @wonderflow @Somefive @FogDong @wangyikewxgm @anoop2811
-go.*                               @chivalryq @wonderflow @Somefive @FogDong @wangyikewxgm @anoop2811
+.github/                           @chivalryq @wonderflow @Somefive @FogDong @wangyikewxgm @anoop2811 @briankane @jguionnet
+makefiles                          @chivalryq @wonderflow @Somefive @FogDong @wangyikewxgm @anoop2811 @briankane @jguionnet
+go.*                               @chivalryq @wonderflow @Somefive @FogDong @wangyikewxgm @anoop2811 @briankane @jguionnet
 
diff --git a/.github/actions/deploy-current-branch/README.md b/.github/actions/deploy-current-branch/README.md
new file mode 100644
index 00000000..6f97c762
--- /dev/null
+++ b/.github/actions/deploy-current-branch/README.md
@@ -0,0 +1,35 @@
+# Deploy Current Branch Action
+
+This GitHub composite action builds a Docker image from the current branch commit and deploys it to a KubeVela cluster for development testing.
+
+## What it does
+
+- Generates a unique image tag from the latest commit hash
+- Builds and loads the Docker image into a KinD cluster
+- Applies KubeVela CRDs for upgrade safety
+- Upgrades the KubeVela Helm release to use the local development image
+- Verifies deployment status and the running image version
+
+## Usage
+
+```yaml
+- name: Deploy Current Branch
+  uses: ./path/to/this/action
+```
+
+## Requirements
+
+- Docker, Helm, kubectl, and KinD must be available in your runner environment
+- Kubernetes cluster access
+- `charts/vela-core/crds` directory with CRDs
+- Valid Helm chart at `charts/vela-core`
+
+## Steps performed
+
+1. **Generate commit hash for image tag**
+2. **Build & load Docker image into KinD**
+3. **Pre-apply chart CRDs**
+4. **Upgrade KubeVela using local image**
+5. **Verify deployment and image version**
+
+---
\ No newline at end of file
diff --git a/.github/actions/deploy-current-branch/action.yaml b/.github/actions/deploy-current-branch/action.yaml
new file mode 100644
index 00000000..5e296cd4
--- /dev/null
+++ b/.github/actions/deploy-current-branch/action.yaml
@@ -0,0 +1,89 @@
+name: 'Deploy Current Branch'
+description: 'Builds Docker image from current branch commit and deploys it to KubeVela cluster for development testing'
+
+runs:
+  using: "composite"
+  steps:
+    # ========================================================================
+    # Git Commit Hash Generation
+    # Generate unique image tag from current branch's latest commit
+    # ========================================================================
+    - name: Get commit hash
+      id: commit_hash
+      shell: bash
+      run: |
+        COMMIT_HASH="git-$(git rev-parse --short HEAD)"
+        echo "Using commit hash: $COMMIT_HASH"
+        echo "COMMIT_HASH=$COMMIT_HASH" >> $GITHUB_ENV
+
+    # ========================================================================
+    # Docker Image Build and Cluster Loading
+    # Build development image from current code and load into KinD cluster
+    # ========================================================================
+    - name: Build and load Docker image
+      shell: bash
+      run: |
+        echo "Building development image: vela-core-test:${{ env.COMMIT_HASH }}"
+
+        mkdir -p $HOME/tmp/
+
+        docker build --no-cache \
+          -t vela-core-test:${{ env.COMMIT_HASH }} \
+          -f Dockerfile .
+
+        echo "Loading image into KinD cluster..."
+        TMPDIR=$HOME/tmp/ kind load docker-image vela-core-test:${{ env.COMMIT_HASH }}
+
+    # ========================================================================
+    # Custom Resource Definitions Application
+    # Pre-apply CRDs to ensure upgrade compatibility and prevent conflicts
+    # ========================================================================
+    - name: Pre-apply CRDs from target chart (upgrade-safe)
+      shell: bash
+      run: |
+        CRD_DIR="charts/vela-core/crds"
+
+        echo "Applying CRDs idempotently..."
+        kubectl apply -f "${CRD_DIR}"
+
+    # ========================================================================
+    # KubeVela Helm Chart Upgrade
+    # Upgrade existing installation to use locally built development image
+    # ========================================================================
+    - name: Upgrade KubeVela to development image
+      shell: bash
+      run: |
+        echo "Upgrading KubeVela to development version..."
+        helm upgrade kubevela ./charts/vela-core \
+          --namespace vela-system \
+          --set image.repository=vela-core-test \
+          --set image.tag=${{ env.COMMIT_HASH }} \
+          --set image.pullPolicy=IfNotPresent \
+          --timeout 5m \
+          --wait \
+          --debug
+
+    # ========================================================================
+    # Deployment Status Verification
+    # Verify successful upgrade and confirm correct image deployment
+    # ========================================================================
+    - name: Verify deployment status
+      shell: bash
+      run: |
+        echo "=== DEPLOYMENT VERIFICATION ==="
+        echo "Verifying upgrade to local development image..."
+
+        echo "--- Pod Status ---"
+        kubectl get pods -n vela-system
+
+        echo "--- Deployment Rollout ---"
+        kubectl rollout status deployment/kubevela-vela-core \
+          -n vela-system \
+          --timeout=300s
+
+        echo "--- Deployed Image Version ---"
+        kubectl get deployment kubevela-vela-core \
+          -n vela-system \
+          -o yaml | grep "image:" | head -1
+
+        echo "Deployment verification completed successfully!"
\ No newline at end of file
diff --git a/.github/actions/deploy-latest-release/README.md b/.github/actions/deploy-latest-release/README.md
new file mode 100644
index 00000000..4eabbc39
--- /dev/null
+++ b/.github/actions/deploy-latest-release/README.md
@@ -0,0 +1,32 @@
+# Install Latest KubeVela Release Action
+
+This GitHub composite action installs the latest stable KubeVela release from the official Helm repository and verifies its deployment status.
+
+## What it does
+
+- Discovers the latest stable KubeVela release tag from GitHub
+- Adds and updates the official KubeVela Helm chart repository
+- Installs KubeVela into the `vela-system` namespace (using Helm)
+- Verifies pod status and deployment rollout for successful installation
+
+## Usage
+
+```yaml
+- name: Install Latest KubeVela Release
+  uses: ./path/to/this/action
+```
+
+## Requirements
+
+- Helm, kubectl, jq, and curl must be available in your runner environment
+- Kubernetes cluster access
+
+## Steps performed
+
+1. **Release Tag Discovery:** Fetches latest stable tag (without `v` prefix)
+2. **Helm Repo Setup:** Adds/updates KubeVela Helm chart repo
+3. **Install KubeVela:** Installs latest release in the `vela-system` namespace
+4. **Status Verification:** Checks pod status and rollout for readiness
+
+---
+
diff --git a/.github/actions/deploy-latest-release/action.yaml b/.github/actions/deploy-latest-release/action.yaml
new file mode 100644
index 00000000..05e0115e
--- /dev/null
+++ b/.github/actions/deploy-latest-release/action.yaml
@@ -0,0 +1,68 @@
+name: 'Install Latest KubeVela Release'
+description: 'Installs the latest stable KubeVela release from official Helm repository with status verification'
+
+runs:
+  using: "composite"
+  steps:
+    # ========================================================================
+    # Latest Release Tag Discovery
+    # Fetch current stable release version from GitHub API
+    # ========================================================================
+    - name: Get latest KubeVela release tag (no v prefix)
+      id: get_latest_tag
+      shell: bash
+      run: |
+        TAG=$(curl -s https://api.github.com/repos/kubevela/kubevela/releases/latest | \
+              jq -r ".tag_name" | \
+              awk '{sub(/^v/, ""); print}')
+        echo "LATEST_TAG=$TAG" >> $GITHUB_ENV
+        echo "Discovered latest release: $TAG"
+
+    # ========================================================================
+    # Helm Repository Configuration
+    # Add and update official KubeVela chart repository
+    # ========================================================================
+    - name: Add KubeVela Helm repo
+      shell: bash
+      run: |
+        echo "Adding KubeVela Helm repository..."
+        helm repo add kubevela https://kubevela.github.io/charts
+        helm repo update
+        echo "Helm repository configuration completed"
+
+    # ========================================================================
+    # KubeVela Stable Release Installation
+    # Deploy latest stable version to vela-system namespace
+    # ========================================================================
+    - name: Install KubeVela ${{ env.LATEST_TAG }}
+      shell: bash
+      run: |
+        echo "Installing KubeVela version: ${{ env.LATEST_TAG }}"
+        helm install \
+          --create-namespace \
+          -n vela-system \
+          kubevela kubevela/vela-core \
+          --version ${{ env.LATEST_TAG }} \
+          --timeout 10m \
+          --wait
+        echo "KubeVela installation completed"
+
+    # ========================================================================
+    # Installation Status Verification
+    # Verify successful deployment and readiness of KubeVela components
+    # ========================================================================
+    - name: Post-install status
+      shell: bash
+      run: |
+        echo "=== INSTALLATION VERIFICATION ==="
+        echo "Verifying KubeVela deployment status..."
+
+        echo "--- Pod Status ---"
+        kubectl get pods -n vela-system
+
+        echo "--- Deployment Rollout ---"
+        kubectl rollout status deployment/kubevela-vela-core \
+          -n vela-system \
+          --timeout=300s
+
+        echo "KubeVela installation verification completed successfully!"
\ No newline at end of file
diff --git a/.github/actions/env-setup/README.md b/.github/actions/env-setup/README.md
new file mode 100644
index 00000000..a4cd04d2
--- /dev/null
+++ b/.github/actions/env-setup/README.md
@@ -0,0 +1,67 @@
+# Kubevela Test Environment Setup Action
+
+A GitHub Actions composite action that sets up a complete testing environment for Kubevela projects with Go, Kubernetes tools, and the Ginkgo testing framework.
+
+## Features
+
+- 🛠️ **System Dependencies**: Installs essential build tools (make, gcc, jq, curl, etc.)
+- ☸️ **Kubernetes Tools**: Sets up kubectl and Helm for cluster operations
+- 🐹 **Go Environment**: Configurable Go version with module caching
+- 📦 **Dependency Management**: Downloads and verifies Go module dependencies
+- 🧪 **Testing Framework**: Installs Ginkgo v2 for BDD-style testing
+
+## Usage
+
+```yaml
+- name: Setup Kubevela Test Environment
+  uses: ./path/to/this/action
+  with:
+    go-version: '1.23.8'      # Optional: Go version (default: 1.23.8)
+```
+
+### Example Workflow
+
+```yaml
+name: Kubevela Tests
+on: [push, pull_request]
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      
+      - name: Setup Test Environment
+        uses: ./path/to/this/action
+        with:
+          go-version: '1.21'
+          
+      - name: Run Tests
+        run: |
+          ginkgo -r ./tests/e2e/
+```
+
+## Inputs
+
+| Input | Description | Required | Default | Usage |
+|-------|-------------|----------|---------|-------|
+| `go-version` | Go version to install and use | No | `1.23.8` | Specify Go version for your project |
+
+## What This Action Installs
+
+### System Tools
+- **make**: Build automation tool
+- **gcc**: GNU Compiler Collection
+- **jq**: JSON processor for shell scripts
+- **ca-certificates**: SSL/TLS certificates
+- **curl**: HTTP client for downloads
+- **gnupg**: GNU Privacy Guard for security
+
+### Kubernetes Ecosystem
+- **kubectl**: Kubernetes command-line tool (latest stable)
+- **helm**: Kubernetes package manager (latest stable)
+
+### Go Development
+- **Go Runtime**: Specified version with module caching enabled
+- **Go Modules**: Downloaded and verified dependencies
+- **Ginkgo v2.14.0**: BDD testing framework for Go
\ No newline at end of file
diff --git a/.github/actions/env-setup/action.yaml b/.github/actions/env-setup/action.yaml
new file mode 100644
index 00000000..c41a6158
--- /dev/null
+++ b/.github/actions/env-setup/action.yaml
@@ -0,0 +1,72 @@
+name: 'Kubevela Test Environment Setup'
+description: 'Sets up complete testing environment for Kubevela with Go, Kubernetes tools, and Ginkgo framework for E2E testing.'
+
+inputs:
+  go-version:
+    description: 'Go version to use for testing'
+    required: false
+    default: '1.23.8'
+
+
+runs:
+  using: 'composite'
+  steps:
+    # ========================================================================
+    # Environment Setup
+    # ========================================================================
+    - name: Install system dependencies
+      shell: bash
+      run: |
+        # Update package manager and install essential tools
+        sudo apt-get update
+        sudo apt-get install -y \
+          make \
+          gcc \
+          jq \
+          ca-certificates \
+          curl \
+          gnupg
+
+    - name: Install kubectl and helm
+      shell: bash
+      run: |
+        # Detect architecture
+        ARCH=$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/')
+        
+        # Install kubectl
+        echo "Installing kubectl for architecture: $ARCH"
+        curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/${ARCH}/kubectl"
+        chmod +x kubectl
+        sudo mv kubectl /usr/local/bin/
+        
+        # Install helm using the official script (more reliable)
+        echo "Installing Helm using official script..."
+        curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
+        chmod 700 get_helm.sh
+        ./get_helm.sh
+        rm get_helm.sh
+        
+        # Verify installations
+        echo "Verifying installations..."
+        kubectl version --client
+        helm version
+    
+
+    - name: Setup Go environment
+      uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32
+      with:
+        go-version: ${{ inputs.go-version }}
+        cache: true
+
+    - name: Download Go dependencies
+      shell: bash
+      run: |
+        # Download and cache Go module dependencies
+        go mod download
+        go mod verify
+
+    - name: Install Ginkgo testing framework
+      shell: bash
+      run: |
+        echo "Installing Ginkgo testing framework..."
+        go install github.com/onsi/ginkgo/v2/ginkgo@v2.14.0
diff --git a/.github/actions/setup-kind-cluster/README.md b/.github/actions/setup-kind-cluster/README.md
new file mode 100644
index 00000000..aaf70982
--- /dev/null
+++ b/.github/actions/setup-kind-cluster/README.md
@@ -0,0 +1,78 @@
+# Setup Kind Cluster Action
+
+A GitHub Action that sets up a Kubernetes testing environment using Kind (Kubernetes in Docker) for E2E testing.
+
+## Inputs
+
+| Input | Description | Required | Default |
+|-------|-------------|----------|---------|
+| `k8s-version` | Kubernetes version for the kind cluster | No | `v1.31.9` |
+
+## Quick Start
+
+```yaml
+name: E2E Tests
+on: [push, pull_request]
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      
+      - uses: actions/setup-go@v5
+        with:
+          go-version: '1.21'
+      
+      - name: Setup Kind Cluster
+        uses: ./.github/actions/setup-kind-cluster
+        with:
+          k8s-version: 'v1.31.9'
+      
+      - name: Run tests
+        run: |
+          kubectl cluster-info
+          make test-e2e
+```
+
+## What it does
+
+1. **Installs Kind CLI** - Downloads Kind v0.29.0 using Go
+2. **Cleans up** - Removes any existing Kind clusters
+3. **Creates cluster** - Spins up Kubernetes v1.31.9 cluster
+4. **Sets up environment** - Configures KUBECONFIG for kubectl access
+5. **Loads images** - Builds and loads Docker images using `make image-load`
+
+## File Structure
+
+Save as `.github/actions/setup-kind-cluster/action.yaml`:
+
+```yaml
+name: 'SetUp kind cluster'
+description: 'Sets up complete testing environment for Kubevela with Go, Kubernetes tools, and Ginkgo framework for E2E testing.'
+
+inputs:
+  k8s-version:
+    description: 'Kubernetes version for the kind cluster'
+    required: false
+    default: 'v1.31.9'
+
+runs:
+  using: 'composite'
+  steps:
+    # ========================================================================
+    # Kind cluster Setup
+    # ========================================================================
+    - name: Setup KinD
+      run: |
+        go install sigs.k8s.io/kind@v0.29.0
+        kind delete cluster || true
+        kind create cluster --image=kindest/node:${{ inputs.k8s-version }}
+      shell: bash
+
+    - name: Load image
+      run: |
+        mkdir -p $HOME/tmp/
+        TMPDIR=$HOME/tmp/ make image-load
+      shell: bash
+```
\ No newline at end of file
diff --git a/.github/actions/setup-kind-cluster/action.yaml b/.github/actions/setup-kind-cluster/action.yaml
new file mode 100644
index 00000000..824c7828
--- /dev/null
+++ b/.github/actions/setup-kind-cluster/action.yaml
@@ -0,0 +1,36 @@
+name: 'SetUp kind cluster'
+description: 'Sets up a KinD (Kubernetes in Docker) cluster with configurable Kubernetes version and optional cluster naming for testing and development workflows.'
+inputs:
+  k8s-version:
+    description: 'Kubernetes version for the kind cluster'
+    required: false
+    default: 'v1.31.9'
+  name:
+    description: 'Name of the kind cluster'
+    required: false
+runs:
+  using: 'composite'
+  steps:
+    # ========================================================================
+    # Kind cluster Setup
+    # ========================================================================
+    - name: Setup KinD
+      run: |
+        go install sigs.k8s.io/kind@v0.29.0
+        if [ -n "${{ inputs.name }}" ]; then
+          kind delete cluster --name="${{ inputs.name }}" || true
+          kind create cluster --name="${{ inputs.name }}" --image=kindest/node:${{ inputs.k8s-version }}
+          kind export kubeconfig --internal --name="${{ inputs.name }}" --kubeconfig /tmp/${{ inputs.name }}.kubeconfig
+        else
+          kind delete cluster || true
+          kind create cluster --image=kindest/node:${{ inputs.k8s-version }}
+        fi
+      shell: bash
+
+    - name: Load image
+      run: |
+        if [ -z "${{ inputs.name }}" ]; then
+          mkdir -p $HOME/tmp/
+          TMPDIR=$HOME/tmp/ make image-load
+        fi
+      shell: bash
\ No newline at end of file
diff --git a/.github/comment.userlist b/.github/comment.userlist
index c3f04aff..785e8fd8 100644
--- a/.github/comment.userlist
+++ b/.github/comment.userlist
@@ -11,4 +11,7 @@ wangyuan249
 chivalryq
 FogDong
 leejanee
-barnettZQG
\ No newline at end of file
+barnettZQG
+anoop2811 
+briankane 
+jguionnet
\ No newline at end of file
diff --git a/.github/workflows/definition-lint.yml b/.github/workflows/definition-lint.yml
index 1714f5c3..bdfa61c0 100644
--- a/.github/workflows/definition-lint.yml
+++ b/.github/workflows/definition-lint.yml
@@ -33,9 +33,9 @@ jobs:
           submodules: true
 
       - name: Setup KinD
-        run: |
-          go install sigs.k8s.io/kind@v0.29.0
-          kind create cluster --image=kindest/node:v1.31.9
+        uses: ./.github/actions/setup-kind-cluster
+        with:
+          name: linter
 
       - name: Definition Doc generate check
         run: |
diff --git a/.github/workflows/e2e-multicluster-test.yml b/.github/workflows/e2e-multicluster-test.yml
index b2be990c..5ac8d12a 100644
--- a/.github/workflows/e2e-multicluster-test.yml
+++ b/.github/workflows/e2e-multicluster-test.yml
@@ -44,7 +44,7 @@ jobs:
     if: needs.detect-noop.outputs.noop != 'true'
     strategy:
       matrix:
-        k8s-version: ["v1.31"]
+        k8s-version: ["v1.31.9"]
     concurrency:
       group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.k8s-version }}
       cancel-in-progress: true
@@ -53,61 +53,31 @@ jobs:
       - name: Check out code into the Go module directory
         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
 
-      - name: Install tools
-        run: |
-          sudo apt-get update
-          sudo apt-get install make gcc jq ca-certificates curl gnupg -y
-          sudo snap install kubectl --classic
-          sudo snap install helm --classic
-
-      - name: Setup Go
-        uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5
+      - name: Setup worker cluster kinD
+        uses: ./.github/actions/setup-kind-cluster
         with:
-          go-version: ${{ env.GO_VERSION }}
-
-      - name: Get dependencies
-        run: |
-          go get -v -t -d ./...
-
-      - name: Setup KinD
-        run: |
-          go install sigs.k8s.io/kind@v0.29.0
-          kind delete cluster --name worker || true
-          kind create cluster --name worker --image=kindest/node:v1.31.9
-          kind export kubeconfig --internal --name worker --kubeconfig /tmp/worker.kubeconfig
-          kind delete cluster || true
-          kind create cluster --image=kindest/node:v1.31.9
-
-      - name: Load image
-        run: |
-          mkdir -p $HOME/tmp/ 
-          TMPDIR=$HOME/tmp/ make image-load
+          name: worker
+          k8s-version: ${{ matrix.k8s-version }}
 
-      - name: Cleanup for e2e tests
-        run: |
-          make vela-cli
-          make e2e-cleanup
-          make e2e-setup-core-auth
-
-      - name: Run e2e multicluster tests
-        run: |
-          export PATH=$(pwd)/bin:$PATH
-          make e2e-multicluster-test
-
-      - name: Stop kubevela, get profile
-        run: |
-          make end-e2e-core-shards
+      - name: Setup master cluster kinD
+        uses: ./.github/actions/setup-kind-cluster
+        with:
+          k8s-version: ${{ matrix.k8s-version }}
 
-      - name: Upload coverage report
-        uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24
+      - name: Run upgrade multicluster tests
+        uses: ./.github/actions/multicluster-test
         with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-          files: /tmp/e2e-profile.out,/tmp/e2e_multicluster_test.out
-          flags: e2e-multicluster-test
-          name: codecov-umbrella
+          codecov-enable: true
+          codecov-token: ${{ secrets.CODECOV_TOKEN }}
 
       - name: Clean e2e profile
-        run: rm /tmp/e2e-profile.out
+        run: |
+          if [ -f /tmp/e2e-profile.out ]; then
+            rm /tmp/e2e-profile.out
+            echo "E2E profile cleaned"
+          else
+            echo "E2E profile not found, skipping cleanup"
+          fi
 
       - name: Cleanup image
         if: ${{ always() }}
diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml
index de063b15..ee46361a 100644
--- a/.github/workflows/e2e-test.yml
+++ b/.github/workflows/e2e-test.yml
@@ -53,68 +53,26 @@ jobs:
       - name: Check out code into the Go module directory
         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
 
-      - name: Install tools
-        run: |
-          sudo apt-get update
-          sudo apt-get install make gcc jq ca-certificates curl gnupg -y
-          sudo snap install kubectl --classic
-          sudo snap install helm --classic
-
-      - name: Setup Go
-        uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5
-        with:
-          go-version: ${{ env.GO_VERSION }}
-
-      - name: Get dependencies
-        run: |
-          go get -v -t -d ./...
-
       - name: Setup KinD
-        run: |
-          go install sigs.k8s.io/kind@v0.29.0
-          kind delete cluster || true
-          kind create cluster --image=kindest/node:v1.31.9
+        uses: ./.github/actions/setup-kind-cluster
 
-      - name: Get Ginkgo
-        run: |
-          go install github.com/onsi/ginkgo/v2/ginkgo@v2.14.0
-
-      - name: Load image
-        run: |
-          mkdir -p $HOME/tmp/
-          TMPDIR=$HOME/tmp/ make image-load
-
-      - name: Run Make
-        run: make
-
-      - name: Prepare for e2e tests
-        run: |
-          make e2e-cleanup
-          make e2e-setup-core
-          helm test -n vela-system kubevela --timeout 5m
-
-      - name: Run api e2e tests
-        run: make e2e-api-test
-
-      - name: Run addons e2e tests
-        run: make e2e-addon-test
-
-      - name: Run e2e tests
-        run: make e2e-test
-
-      - name: Stop kubevela, get profile
-        run: make end-e2e
-
-      - name: Upload coverage report
-        uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24
+      # ========================================================================
+      # E2E Test Execution
+      # ========================================================================
+      - name: Run upgrade e2e tests
+        uses: ./.github/actions/e2e-test
         with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-          files: /tmp/e2e-profile.out
-          flags: e2etests
-          name: codecov-umbrella
+          codecov-enable: true
+          codecov-token: ${{ secrets.CODECOV_TOKEN }}
 
       - name: Clean e2e profile
-        run: rm /tmp/e2e-profile.out
+        run: |
+          if [ -f /tmp/e2e-profile.out ]; then
+            rm /tmp/e2e-profile.out
+            echo "E2E profile cleaned"
+          else
+            echo "E2E profile not found, skipping cleanup"
+          fi
 
       - name: Cleanup image
         if: ${{ always() }}
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 1de8a4aa..1549f8c0 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -97,28 +97,16 @@ jobs:
         with:
           submodules: true
 
-      - name: Setup Go
-        uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5
-        with:
-          go-version: ${{ env.GO_VERSION }}
-
+      - name: Setup Env
+        uses: ./.github/actions/env-setup
+        
       - name: Setup node
         uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
         with:
           node-version: "14"
 
-      - name: Cache Go Dependencies
-        uses: actions/cache@v4
-        with:
-          path: .work/pkg
-          key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
-          restore-keys: ${{ runner.os }}-pkg-
-
-      - name: Setup KinD
-        run: |
-          go install sigs.k8s.io/kind@v0.29.0
-          kind delete cluster --name kind || true
-          kind create cluster --name kind --image=kindest/node:v1.31.9 --kubeconfig ~/.kube/config
+      - name: Setup kinD
+        uses: ./.github/actions/setup-kind-cluster
 
       - name: Run cross-build
         run: make cross-build
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 078126ca..e13e73fa 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -3,7 +3,7 @@ name: Release
 on:
   push:
     tags:
-      - 'v*'
+      - "v*"
   workflow_dispatch: {}
 
 permissions:
@@ -26,6 +26,32 @@ jobs:
     outputs:
       hashes: ${{ steps.hash.outputs.hashes }}
     steps:
+      - name: Check disk (before)
+        run: |
+          df -h
+          sudo du -sh /usr/local/lib/android /usr/share/dotnet /opt/ghc || true
+
+      - name: Free Disk Space (Ubuntu)
+        uses: insightsengineering/disk-space-reclaimer@v1
+        with:
+          # this might remove tools that are actually needed,
+          # if set to "true" but frees about 6 GB
+          tools-cache: false
+          # all of these default to true, but feel free to set to
+          # "false" if necessary for your workflow
+          android: true
+          dotnet: true
+          haskell: true
+          large-packages: true
+          swap-storage: true
+          docker-images: true
+
+      # Extra prune in case your job builds/pulls images
+      - name: Deep Docker prune
+        run: |
+          docker system prune -af || true
+          docker builder prune -af || true
+
       - name: Checkout
         uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
         with:
@@ -43,7 +69,7 @@ jobs:
       - name: Install Cosign
         uses: sigstore/cosign-installer@main
         with:
-          cosign-release: 'v2.5.0'
+          cosign-release: "v2.5.0"
 
       - name: Install syft
         uses: anchore/sbom-action/download-syft@f325610c9f50a54015d37c8d16cb3b0e2c8f4de0 # v0.18.0
@@ -65,6 +91,9 @@ jobs:
           HASHES=$(find dist -type f -exec sha256sum {} \; | base64 -w0)
           echo "hashes=$HASHES" >> "$GITHUB_OUTPUT"
 
+      - name: Check disk (after)
+        run: df -h
+
   upload-plugin-homebrew:
     name: upload-sha256sums
     needs: goreleaser
@@ -82,16 +111,9 @@ jobs:
     steps:
       - name: Checkout
         uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
+
       - name: Update kubectl plugin version in krew-index
         uses: rajatjindal/krew-release-bot@df3eb197549e3568be8b4767eec31c5e8e8e6ad8 # v0.0.46
-      - name: Update Homebrew formula
-        uses: dawidd6/action-homebrew-bump-formula@d3667e5ae14df19579e4414897498e3e88f2f458 # v3.10.0
-        with:
-          token: ${{ secrets.HOMEBREW_TOKEN }}
-          formula: kubevela
-          tag: ${{ github.ref }}
-          revision: ${{ github.sha }}
-          force: false
 
   provenance-vela-bins:
     name: generate provenance for binaries
@@ -103,5 +125,5 @@ jobs:
       actions: read
     uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 # has to be sem var
     with:
-      base64-subjects: '${{ needs.goreleaser.outputs.hashes }}'
+      base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
       upload-assets: true
diff --git a/.github/workflows/sdk-test.yml b/.github/workflows/sdk-test.yml
index 02cbd632..b150d7c1 100644
--- a/.github/workflows/sdk-test.yml
+++ b/.github/workflows/sdk-test.yml
@@ -16,11 +16,6 @@ on:
 permissions:
   contents: read
 
-env:
-  # Common versions
-  GO_VERSION: '1.23.8'
-  GOLANGCI_VERSION: 'v1.60.1'
-
 jobs:
   sdk-tests:
     runs-on: ubuntu-22.04
@@ -28,10 +23,8 @@ jobs:
       - name: Check out code into the Go module directory
         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
 
-      - name: Setup Go
-        uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5
-        with:
-          go-version: ${{ env.GO_VERSION }}
+      - name: Setup Env
+        uses: ./.github/actions/env-setup
 
       - name: Install Go tools
         run: |
@@ -39,9 +32,9 @@ jobs:
           make golangci
 
       - name: Setup KinD
-        run: |
-          go install sigs.k8s.io/kind@v0.29.0
-          kind create cluster --image=kindest/node:v1.31.9
+        uses: ./.github/actions/setup-kind-cluster
+        with:
+          name: sdk-test
 
       - name: Build CLI
         run: make vela-cli
diff --git a/.github/workflows/sync-api.yml b/.github/workflows/sync-api.yml
index 359d92e4..f5f93877 100644
--- a/.github/workflows/sync-api.yml
+++ b/.github/workflows/sync-api.yml
@@ -10,21 +10,16 @@ on:
 permissions:
   contents: read
 
-env:
-  GO_VERSION: '1.23.8'
-
 jobs:
   sync-core-api:
     runs-on: ubuntu-22.04
     steps:
-      - name: Set up Go
-        uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491
-        with:
-          go-version: ${{ env.GO_VERSION }}
-
       - name: Check out code into the Go module directory
         uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
 
+      - name: Setup Env
+        uses: ./.github/actions/env-setup
+
       - name: Get the version
         id: get_version
         run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
diff --git a/.github/workflows/sync-sdk.yaml b/.github/workflows/sync-sdk.yaml
index ef21596a..64738572 100644
--- a/.github/workflows/sync-sdk.yaml
+++ b/.github/workflows/sync-sdk.yaml
@@ -14,36 +14,33 @@ on:
 permissions:
   contents: read
 
-env:
-  GO_VERSION: '1.23.8'
-
 jobs:
   sync_sdk:
     runs-on: ubuntu-22.04
     steps:
-      - name: Set up Go
-        uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491
-        with:
-          go-version: ${{ env.GO_VERSION }}
 
       - name: Check out code into the Go module directory
         uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
 
-      - name: Get the version
-        id: get_version
-        run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
-
-      - name: Get dependencies
-        run: |
-          go get -v -t -d ./...
+      - name: Env setup
+        uses: ./.github/actions/env-setup
 
       - name: Install Go tools
         run: |
           make goimports
-          
+
       - name: Build CLI
         run: make vela-cli
 
+      - name: Setup KinD
+        uses: ./.github/actions/setup-kind-cluster
+        with:
+          name: sync-sdk
+
+      - name: Get the version
+        id: get_version
+        run: echo "VERSION=${GITHUB_REF}" >> $GITHUB_OUTPUT
+
       - name: Sync SDK to kubevela/kubevela-go-sdk
         run: bash ./hack/sdk/sync.sh
         env:
diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml
index ed78a19b..4e24a315 100644
--- a/.github/workflows/unit-test.yml
+++ b/.github/workflows/unit-test.yml
@@ -14,10 +14,6 @@ on:
 permissions:
   contents: read
 
-env:
-  # Common versions
-  GO_VERSION: "1.23.8"
-
 jobs:
   detect-noop:
     permissions:
@@ -41,41 +37,19 @@ jobs:
     if: needs.detect-noop.outputs.noop != 'true'
 
     steps:
-      - name: Set up Go
-        uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5
-        with:
-          go-version: ${{ env.GO_VERSION }}
-
       - name: Check out code into the Go module directory
         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
         with:
           submodules: true
 
-      - name: Cache Go Dependencies
-        uses: actions/cache@v4
-        with:
-          path: .work/pkg
-          key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
-          restore-keys: ${{ runner.os }}-pkg-
-
-      - name: Install ginkgo
-        run: |
-          sudo sed -i 's/azure\.//' /etc/apt/sources.list
-          sudo apt-get update
-          sudo apt-get install -y golang-ginkgo-dev
-
-      - name: Setup KinD
-        run: |
-          go install sigs.k8s.io/kind@v0.29.0
-          kind create cluster --image=kindest/node:v1.31.9
+      - name: Setup Env
+        uses: ./.github/actions/env-setup
 
-      - name: Run Make test
-        run: make test
+      - name: Setup KinD with Kubernetes
+        uses: ./.github/actions/setup-kind-cluster
 
-      - name: Upload coverage report
-        uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 #v5.4.3
+      - name: Run unit tests
+        uses: ./.github/actions/unit-test
         with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-          files: ./coverage.txt
-          flags: core-unittests
-          name: codecov-umbrella
+          codecov-enable: true
+          codecov-token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/upgrade-e2e-multicluster-test.yml b/.github/workflows/upgrade-e2e-multicluster-test.yml
new file mode 100644
index 00000000..b120efac
--- /dev/null
+++ b/.github/workflows/upgrade-e2e-multicluster-test.yml
@@ -0,0 +1,98 @@
+# =============================================================================
+# E2E Upgrade Multicluster Test Workflow
+# =============================================================================
+# This workflow performs end-to-end testing for KubeVela multicluster upgrades.
+# It tests the upgrade path from the latest released version to the current
+# development branch across multiple Kubernetes versions.
+#
+# Test Flow:
+# 1. Install latest KubeVela release
+# 2. Build and upgrade to current development version
+# 3. Run multicluster e2e tests to verify functionality
+# =============================================================================
+
+name: E2E Upgrade Multicluster Test
+
+# =============================================================================
+# Trigger Configuration
+# =============================================================================
+on:
+  # Trigger on pull requests targeting main branches
+  pull_request:
+    branches:
+      - master
+      - release-*
+
+  # Allow manual workflow execution
+  workflow_dispatch: {}
+
+# =============================================================================
+# Security Configuration
+# =============================================================================
+permissions:
+  contents: read  # Read-only access to repository contents
+
+# =============================================================================
+# Global Environment Variables
+# =============================================================================
+env:
+  GO_VERSION: '1.23.8'  # Go version for building and testing
+
+# =============================================================================
+# Job Definitions
+# =============================================================================
+jobs:
+  upgrade-multicluster-tests:
+    name: Upgrade Multicluster Tests
+    runs-on: ubuntu-22.04
+    if: startsWith(github.head_ref, 'chore/upgrade-k8s-')
+    timeout-minutes: 60  # Prevent hanging jobs
+
+    # ==========================================================================
+    # Matrix Strategy - Test against multiple Kubernetes versions
+    # ==========================================================================
+    strategy:
+      fail-fast: false  # Continue testing other versions if one fails
+      matrix:
+        k8s-version: ['v1.31.9']
+
+    # ==========================================================================
+    # Concurrency Control - Prevent overlapping runs
+    # ==========================================================================
+    concurrency:
+      group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.k8s-version }}
+      cancel-in-progress: true
+
+    steps:
+      # ========================================================================
+      # Environment Setup
+      # ========================================================================
+
+      - name: Check out repository
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
+
+      # ========================================================================
+      # Kubernetes Cluster Setup
+      # ========================================================================
+
+      - name: Setup worker cluster kinD
+        uses: ./.github/actions/setup-kind-cluster
+        with:
+          name: worker
+
+      - name: Setup KinD master clusters for multicluster testing
+        uses: ./.github/actions/setup-kind-cluster
+        with:
+          k8s-version: ${{ matrix.k8s-version }}
+
+      - name: Deploy latest release
+        uses: ./.github/actions/deploy-latest-release
+
+      - name: Upgrade from current branch
+        uses: ./.github/actions/deploy-current-branch
+
+      - name: Run upgarde multicluster tests
+        uses: ./.github/actions/multicluster-test
+        with:
+          codecov-enable: false
+          codecov-token: ''
\ No newline at end of file
diff --git a/.github/workflows/upgrade-e2e-test.yml b/.github/workflows/upgrade-e2e-test.yml
new file mode 100644
index 00000000..32fc3d8e
--- /dev/null
+++ b/.github/workflows/upgrade-e2e-test.yml
@@ -0,0 +1,102 @@
+# =============================================================================
+# Upgrade E2E Test Workflow
+# =============================================================================
+# This workflow performs comprehensive end-to-end testing for KubeVela upgrades.
+# It validates the upgrade path from the latest stable release to the current
+# development version by running multiple test suites including API, addon,
+# and general e2e tests.
+#
+# Test Flow:
+# 1. Install latest KubeVela release
+# 2. Build and upgrade to current development version
+# 3. Run comprehensive e2e test suites (API, addon, general)
+# 4. Validate upgrade functionality and compatibility
+# =============================================================================
+
+name: Upgrade E2E Test
+
+# =============================================================================
+# Trigger Configuration
+# =============================================================================
+on:
+  # Trigger on pull requests targeting main branches
+  pull_request:
+    branches:
+      - master
+      - release-*
+
+  # Allow manual workflow execution
+  workflow_dispatch: {}
+
+# =============================================================================
+# Environment Variables
+# =============================================================================
+env:
+  GO_VERSION: '1.23.8'
+
+# =============================================================================
+# Security Configuration
+# =============================================================================
+permissions:
+  contents: read  # Read-only access to repository contents
+
+# =============================================================================
+# Job Definitions
+# =============================================================================
+jobs:
+  upgrade-tests:
+    name: Upgrade E2E Tests
+    runs-on: ubuntu-22.04
+    if: startsWith(github.head_ref, 'chore/upgrade-k8s-')
+    timeout-minutes: 90  # Extended timeout for comprehensive e2e testing
+
+    # ==========================================================================
+    # Matrix Strategy - Test against multiple Kubernetes versions
+    # ==========================================================================
+    strategy:
+      fail-fast: false  # Continue testing other versions if one fails
+      matrix:
+        k8s-version: ['v1.31.9']
+
+    # ==========================================================================
+    # Concurrency Control - Prevent overlapping runs
+    # ==========================================================================
+    concurrency:
+      group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.k8s-version }}
+      cancel-in-progress: true
+
+    steps:
+      # ========================================================================
+      # Repository Setup
+      # ========================================================================
+      - name: Check out code
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
+
+      # ========================================================================
+      # Kubernetes Cluster Setup
+      # ========================================================================
+      - name: Setup KinD with Kubernetes ${{ matrix.k8s-version }}
+        uses: ./.github/actions/setup-kind-cluster
+        with:
+          k8s-version: ${{ matrix.k8s-version }}
+
+      - name: Build vela CLI
+        run: make vela-cli
+
+      - name: Build kubectl-vela plugin
+        run: make kubectl-vela
+
+      - name: Install kustomize
+        run: make kustomize
+
+      - name: Deploy latest release
+        uses: ./.github/actions/deploy-latest-release
+
+      - name: Upgrade from current branch
+        uses: ./.github/actions/deploy-current-branch
+
+      # ========================================================================
+      # E2E Test Execution
+      # ========================================================================
+      - name: Run upgrade e2e tests
+        uses: ./.github/actions/e2e-test
\ No newline at end of file
diff --git a/.github/workflows/upgrade-unit-test.yml b/.github/workflows/upgrade-unit-test.yml
new file mode 100644
index 00000000..bc98a78a
--- /dev/null
+++ b/.github/workflows/upgrade-unit-test.yml
@@ -0,0 +1,83 @@
+# =============================================================================
+# Upgrade Unit Test Workflow
+# =============================================================================
+# This workflow performs unit testing for KubeVela upgrades by:
+# 1. Installing the latest stable KubeVela release
+# 2. Building and upgrading to the current development version
+# 3. Running unit tests to validate the upgrade functionality
+# =============================================================================
+
+name: Upgrade Unit Test
+
+# =============================================================================
+# Trigger Configuration
+# =============================================================================
+on:
+  # Trigger on pull requests targeting main and release branches
+  pull_request:
+    branches:
+      - master
+      - release-*
+
+  # Allow manual workflow execution
+  workflow_dispatch: {}
+
+# =============================================================================
+# Security Configuration
+# =============================================================================
+permissions:
+  contents: read  # Read-only access to repository contents
+
+# =============================================================================
+# Job Definitions
+# =============================================================================
+jobs:
+  upgrade-tests:
+    name: Upgrade Unit Tests
+    runs-on: ubuntu-22.04
+    if: startsWith(github.head_ref, 'chore/upgrade-k8s-')
+    timeout-minutes: 45  # Prevent hanging jobs
+
+    # ==========================================================================
+    # Matrix Strategy - Test against multiple Kubernetes versions
+    # ==========================================================================
+    strategy:
+      fail-fast: false  # Continue testing other versions if one fails
+      matrix:
+        k8s-version: ['v1.31.9']
+
+    # ==========================================================================
+    # Concurrency Control - Prevent overlapping runs
+    # ==========================================================================
+    concurrency:
+      group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.k8s-version }}
+      cancel-in-progress: true
+
+    steps:
+      # ========================================================================
+      # Environment Setup
+      # ========================================================================
+
+      - name: Check out code
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
+
+      # ========================================================================
+      # Kubernetes Cluster Setup
+      # ========================================================================
+
+      - name: Setup KinD with Kubernetes ${{ matrix.k8s-version }}
+        uses: ./.github/actions/setup-kind-cluster
+        with:
+          k8s-version: ${{ matrix.k8s-version }}
+
+      - name: Deploy latest release
+        uses: ./.github/actions/deploy-latest-release
+
+      - name: Upgrade from current branch
+        uses: ./.github/actions/deploy-current-branch
+
+      - name: Run unit tests
+        uses: ./.github/actions/unit-test
+        with:
+          codecov-enable: false
+          codecov-token: ''
diff --git a/.github/workflows/webhook-upgrade-validation.yml b/.github/workflows/webhook-upgrade-validation.yml
new file mode 100644
index 00000000..5e8bceb1
--- /dev/null
+++ b/.github/workflows/webhook-upgrade-validation.yml
@@ -0,0 +1,165 @@
+name: Webhook Upgrade Validation
+
+on:
+  push:
+    branches:
+      - master
+      - release-*
+    tags:
+      - v*
+  workflow_dispatch: {}
+  pull_request:
+    branches:
+      - master
+      - release-*
+
+permissions:
+  contents: read
+
+env:
+  GO_VERSION: '1.23.8'
+
+jobs:
+  webhook-upgrade-check:
+    runs-on: ubuntu-22.04
+    timeout-minutes: 30
+    steps:
+      - name: Check out code into the Go module directory
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
+
+      - name: Setup Env
+        uses: ./.github/actions/env-setup
+
+      - name: Setup KinD
+        run: |
+          go install sigs.k8s.io/kind@v0.29.0
+          kind delete cluster || true
+          kind create cluster --image=kindest/node:v1.31.9
+
+      - name: Install KubeVela CLI
+        run: curl -fsSL https://kubevela.io/script/install.sh | bash
+
+      - name: Install KubeVela baseline
+        run: |
+          vela install --set featureGates.enableCueValidation=true
+          kubectl wait --namespace vela-system --for=condition=Available deployment/kubevela-vela-core --timeout=300s
+
+      - name: Prepare failing chart changes
+        run: |
+          cat <<'CHART' > charts/vela-core/templates/defwithtemplate/resource.yaml
+          # Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
+          # Definition source cue file: vela-templates/definitions/internal/resource.cue
+          apiVersion: core.oam.dev/v1beta1
+          kind: TraitDefinition
+          metadata:
+            annotations:
+              definition.oam.dev/description: Add resource requests and limits on K8s pod for your workload which follows the pod spec in path 'spec.template.'
+            name: resource
+            namespace: {{ include "systemDefinitionNamespace" . }}
+          spec:
+            appliesToWorkloads:
+              - deployments.apps
+              - statefulsets.apps
+              - daemonsets.apps
+              - jobs.batch
+              - cronjobs.batch
+            podDisruptive: true
+            schematic:
+              cue:
+                template: |2
+                  let resourceContent = {
+                    resources: {
+                      if parameter.cpu != _|_ if parameter.memory != _|_ if parameter.requests == _|_ if parameter.limits == _|_ {
+                        // +patchStrategy=retainKeys
+                        requests: {
+                          cpu:    parameter.cpu
+                          memory: parameter.memory
+                        }
+                        // +patchStrategy=retainKeys
+                        limits: {
+                          cpu:    parameter.cpu
+                          memory: parameter.memory
+                        }
+                      }
+                      if parameter.requests != _|_ {
+                        // +patchStrategy=retainKeys
+                        requests: {
+                          cpu:    parameter.requests.cpu
+                          memory: parameter.requests.memory
+                        }
+                      }
+                      if parameter.limits != _|_ {
+                        // +patchStrategy=retainKeys
+                        limits: {
+                          cpu:    parameter.limits.cpu
+                          memory: parameter.limits.memory
+                        }
+                      }
+                    }
+                  }
+                  if context.output.spec != _|_ if context.output.spec.template != _|_ {
+                    patch: spec: template: spec: {
+                      // +patchKey=name
+                      containers: [resourceContent]
+                    }
+                  }
+                  if context.output.spec != _|_ if context.output.spec.jobTemplate != _|_ {
+                    patch: spec: jobTemplate: spec: template: spec: {
+                      // +patchKey=name
+                      containers: [resourceContent]
+                    }
+                  }
+                  parameter: {
+                    // +usage=Specify the amount of cpu for requests and limits
+                    cpu?: *1 | number | string
+                    // +usage=Specify the amount of memory for requests and limits
+                    memory?: *"2048Mi" | =~"^([1-9][0-9]{0,63})(E|P|T|G|M|K|Ei|Pi|Ti|Gi|Mi|Ki)$"
+                    // +usage=Specify the resources in requests
+                    requests?: {
+                      // +usage=Specify the amount of cpu for requests
+                      cpu: *1 | number | string
+                      // +usage=Specify the amount of memory for requests
+                      memory: *"2048Mi" | =~"^([1-9][0-9]{0,63})(E|P|T|G|M|K|Ei|Pi|Ti|Gi|Mi|Ki)$"
+                    }
+                    // +usage=Specify the resources in limits
+                    limits?: {
+                      // +usage=Specify the amount of cpu for limits
+                      cpu: *1 | number | string
+                      // +usage=Specify the amount of memory for limits
+                      memory: *"2048Mi" | =~"^([1-9][0-9]{0,63})(E|P|T|G|M|K|Ei|Pi|Ti|Gi|Mi|Ki)$"
+                    }
+                  }
+
+      - name: Load image
+        run: |
+          mkdir -p $HOME/tmp/
+          TMPDIR=$HOME/tmp/ make image-load
+
+      - name: Run Helm upgrade (expected to fail)
+        run: |
+          set +e
+          helm upgrade \
+            --set image.repository=vela-core-test \
+            --set image.tag=$(git rev-parse --short HEAD) \
+            --set featureGates.enableCueValidation=true \
+            --wait kubevela ./charts/vela-core --debug -n vela-system
+          status=$?
+          echo "Helm upgrade exit code: ${status}"
+          if [ $status -eq 0 ]; then
+            echo "Expected helm upgrade to fail" >&2
+            exit 1
+          fi
+          echo "Helm upgrade failed as expected"
+
+      - name: Dump webhook configurations
+        if: ${{ always() }}
+        run: |
+          kubectl get mutatingwebhookconfiguration kubevela-vela-core-admission -o yaml
+          kubectl get validatingwebhookconfiguration kubevela-vela-core-admission -o yaml
+
+      - name: Verify webhook validation remains active
+        run: ginkgo -v --focus-file requiredparam_validation_test.go ./test/e2e-test
+
+      - name: Cleanup kind cluster
+        if: ${{ always() }}
+        run: kind delete cluster --name kind
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 893138b4..97d51584 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,6 +35,14 @@ vendor/
 .vscode
 .history
 
+# Debug binaries generated by VS Code/Delve
+__debug_bin*
+*/__debug_bin*
+
+# Webhook certificates generated at runtime
+k8s-webhook-server/
+options.go.bak 
+
 pkg/test/vela
 config/crd/bases
 _tmp/
diff --git a/app.yaml b/app.yaml
deleted file mode 100644
index e69de29b..00000000
diff --git a/charts/vela-core/README.md b/charts/vela-core/README.md
index 5f695985..8b078362 100644
--- a/charts/vela-core/README.md
+++ b/charts/vela-core/README.md
@@ -100,6 +100,7 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
 | `featureGates.disableWorkflowContextConfigMapCache`          | disable the workflow context's configmap informer cache                                                                                                                                                                          | `true`  |
 | `featureGates.enableCueValidation`                           | enable the strict cue validation for cue required parameter fields                                                                                                                                                               | `false` |
 | `featureGates.enableApplicationStatusMetrics`                | enable application status metrics and structured logging                                                                                                                                                                         | `false` |
+| `featureGates.validateResourcesExist`                        | enable webhook validation to check if resource types referenced in definition templates exist in the cluster                                                                                                                     | `false` |
 
 ### MultiCluster parameters
 
@@ -147,15 +148,16 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
 | `affinity`                                     | Affinity                                                                                                                                                           | `{}`                 |
 | `rbac.create`                                  | Specifies whether a RBAC role should be created                                                                                                                    | `true`               |
 | `logDebug`                                     | Enable debug logs for development purpose                                                                                                                          | `false`              |
+| `devLogs`                                      | Enable formatted logging support for development purpose                                                                                                           | `false`              |
 | `logFilePath`                                  | If non-empty, write log files in this path                                                                                                                         | `""`                 |
 | `logFileMaxSize`                               | Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited.                                         | `1024`               |
 | `kubeClient.qps`                               | The qps for reconcile clients                                                                                                                                      | `400`                |
 | `kubeClient.burst`                             | The burst for reconcile clients                                                                                                                                    | `600`                |
 | `authentication.enabled`                       | Enable authentication framework for applications                                                                                                                   | `false`              |
-| `authentication.definitions.enabled`           | Enable definition permission validation (requires authentication.enabled)                                                                                          | `false`              |
-| `authentication.withUser`                      | Application authentication will impersonate as the request User                                                                                                    | `true`               |
-| `authentication.defaultUser`                   | Application authentication will impersonate as the User if no user provided in Application                                                                         | `kubevela:vela-core` |
+| `authentication.withUser`                      | Application authentication will impersonate as the request User (must be true for security)                                                                        | `true`               |
+| `authentication.defaultUser`                   | Application authentication will impersonate as the User if no user provided or withUser is false                                                                   | `kubevela:vela-core` |
 | `authentication.groupPattern`                  | Application authentication will impersonate as the request Group that matches the pattern                                                                          | `kubevela:*`         |
+| `authorization.definitionValidationEnabled`    | Enable definition permission validation for RBAC checks on definitions                                                                                             | `false`              |
 | `sharding.enabled`                             | When sharding enabled, the controller will run as master mode. Refer to https://github.com/kubevela/kubevela/blob/master/design/vela-core/sharding.md for details. | `false`              |
 | `sharding.schedulableShards`                   | The shards available for scheduling. If empty, dynamic discovery will be used.                                                                                     | `""`                 |
 | `core.metrics.enabled`                         | Enable metrics for vela-core                                                                                                                                       | `false`              |
diff --git a/charts/vela-core/templates/NOTES.txt b/charts/vela-core/templates/NOTES.txt
index 14cb2559..9758dc8f 100644
--- a/charts/vela-core/templates/NOTES.txt
+++ b/charts/vela-core/templates/NOTES.txt
@@ -30,11 +30,35 @@ Welcome to use the KubeVela! Enjoy your shipping application journey!
 
 You can refer to https://kubevela.io for more details.
 
-{{- if and .Values.authentication.enabled (not .Values.authentication.definitions.enabled) }}
+{{- if and .Values.authentication.enabled (not .Values.authentication.withUser) }}
 
-WARNING: Authentication is enabled but definition permission validation is disabled.
-   Applications can reference definitions without RBAC checks.
-   To enable definition permission validation:
-     --set authentication.definitions.enabled=true
-   Ensure users have appropriate RBAC permissions before enabling.
+WARNING: Authentication is enabled but withUser is disabled.
+   This configuration provides NO security benefit:
+   - All applications will run as '{{ .Values.authentication.defaultUser }}' regardless of who creates them
+   - User groups matching '{{ .Values.authentication.groupPattern }}' are still collected but not used effectively
+   - Service account annotations are blocked
+   
+   To enable true user impersonation for security:
+     --set authentication.withUser=true
+{{- end }}
+
+{{- if and (not .Values.authorization.definitionValidationEnabled) (not .Values.authentication.enabled) }}
+
+SECURITY RECOMMENDATION: Both authentication and definition validation are disabled.
+   If KubeVela is running with cluster-admin or other high-level permissions,
+   consider enabling one or both security features:
+
+   1. Authentication with impersonation (recommended for multi-tenant environments):
+      --set authentication.enabled=true 
+      --set authentication.withUser=true
+      This makes KubeVela impersonate the requesting user, applying their RBAC permissions.
+      Note: Both flags must be enabled for user impersonation to work.
+
+   2. Definition permission validation (lightweight RBAC for definitions):
+      --set authorization.definitionValidationEnabled=true
+      This ensures users can only reference definitions they have access to.
+
+   Using both features together provides defense in depth.
+   Without these protections, users can leverage KubeVela's permissions to deploy
+   resources beyond their intended access level.
 {{- end }}
diff --git a/charts/vela-core/templates/admission-webhooks/job-patch/clusterrole.yaml b/charts/vela-core/templates/admission-webhooks/job-patch/clusterrole.yaml
index 96b0ba33..446848df 100644
--- a/charts/vela-core/templates/admission-webhooks/job-patch/clusterrole.yaml
+++ b/charts/vela-core/templates/admission-webhooks/job-patch/clusterrole.yaml
@@ -4,7 +4,7 @@ kind: ClusterRole
 metadata:
   name:  {{ template "kubevela.fullname" . }}-admission
   annotations:
-    "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade
+    "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade,post-rollback
     "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
   labels:
     app: {{ template "kubevela.name" . }}-admission
diff --git a/charts/vela-core/templates/admission-webhooks/job-patch/clusterrolebinding.yaml b/charts/vela-core/templates/admission-webhooks/job-patch/clusterrolebinding.yaml
index 649bb713..85cc592b 100644
--- a/charts/vela-core/templates/admission-webhooks/job-patch/clusterrolebinding.yaml
+++ b/charts/vela-core/templates/admission-webhooks/job-patch/clusterrolebinding.yaml
@@ -4,7 +4,7 @@ kind: ClusterRoleBinding
 metadata:
   name:  {{ template "kubevela.fullname" . }}-admission
   annotations:
-    "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade
+    "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade,post-rollback
     "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
   labels:
     app: {{ template "kubevela.name" . }}-admission
diff --git a/charts/vela-core/templates/admission-webhooks/job-patch/job-patchWebhook.yaml b/charts/vela-core/templates/admission-webhooks/job-patch/job-patchWebhook.yaml
index eb2e9847..55af070f 100644
--- a/charts/vela-core/templates/admission-webhooks/job-patch/job-patchWebhook.yaml
+++ b/charts/vela-core/templates/admission-webhooks/job-patch/job-patchWebhook.yaml
@@ -5,7 +5,7 @@ metadata:
   name: {{ template "kubevela.fullname" . }}-admission-patch
   namespace: {{ .Release.Namespace }}
   annotations:
-    "helm.sh/hook": post-install,post-upgrade
+    "helm.sh/hook": post-install,post-upgrade,post-rollback
     "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
   labels:
     app: {{ template "kubevela.name" . }}-admission-patch
diff --git a/charts/vela-core/templates/admission-webhooks/job-patch/role.yaml b/charts/vela-core/templates/admission-webhooks/job-patch/role.yaml
index cf3d7dd3..535c3221 100644
--- a/charts/vela-core/templates/admission-webhooks/job-patch/role.yaml
+++ b/charts/vela-core/templates/admission-webhooks/job-patch/role.yaml
@@ -5,7 +5,7 @@ metadata:
   name:  {{ template "kubevela.fullname" . }}-admission
   namespace: {{ .Release.Namespace }}
   annotations:
-    "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade
+    "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade,post-rollback
     "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
   labels:
     app: {{ template "kubevela.name" . }}-admission
diff --git a/charts/vela-core/templates/admission-webhooks/job-patch/rolebinding.yaml b/charts/vela-core/templates/admission-webhooks/job-patch/rolebinding.yaml
index cffdc0f5..84259e9c 100644
--- a/charts/vela-core/templates/admission-webhooks/job-patch/rolebinding.yaml
+++ b/charts/vela-core/templates/admission-webhooks/job-patch/rolebinding.yaml
@@ -5,7 +5,7 @@ metadata:
   name:  {{ template "kubevela.fullname" . }}-admission
   namespace: {{ .Release.Namespace }}
   annotations:
-    "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade
+    "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade,post-rollback
     "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
   labels:
     app: {{ template "kubevela.name" . }}-admission
diff --git a/charts/vela-core/templates/admission-webhooks/job-patch/serviceaccount.yaml b/charts/vela-core/templates/admission-webhooks/job-patch/serviceaccount.yaml
index 4e56267d..fe35c96c 100644
--- a/charts/vela-core/templates/admission-webhooks/job-patch/serviceaccount.yaml
+++ b/charts/vela-core/templates/admission-webhooks/job-patch/serviceaccount.yaml
@@ -5,7 +5,7 @@ metadata:
   name:  {{ template "kubevela.fullname" . }}-admission
   namespace: {{ .Release.Namespace }}
   annotations:
-    "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade
+    "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade,post-rollback
     "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
   labels:
     app: {{ template "kubevela.name" . }}-admission
diff --git a/charts/vela-core/templates/admission-webhooks/mutatingWebhookConfiguration.yaml b/charts/vela-core/templates/admission-webhooks/mutatingWebhookConfiguration.yaml
index 73608fcb..b0b0b491 100644
--- a/charts/vela-core/templates/admission-webhooks/mutatingWebhookConfiguration.yaml
+++ b/charts/vela-core/templates/admission-webhooks/mutatingWebhookConfiguration.yaml
@@ -1,4 +1,14 @@
 {{- if .Values.admissionWebhooks.enabled -}}
+{{- /* Preserve existing caBundle on upgrade to avoid breaking admission if hooks fail. */}}
+{{- $mName := printf "%s-admission" (include "kubevela.fullname" .) -}}
+{{- $existing := (lookup "admissionregistration.k8s.io/v1" "MutatingWebhookConfiguration" "" $mName) -}}
+{{- $vals := dict "apps" "" "comps" "" -}}
+{{- if $existing -}}
+{{- range $existing.webhooks -}}
+{{- if eq .name "mutating.core.oam.dev.v1beta1.applications" -}}{{- $_ := set $vals "apps" .clientConfig.caBundle -}}{{- end -}}
+{{- if eq .name "mutating.core.oam-dev.v1beta1.componentdefinitions" -}}{{- $_ := set $vals "comps" .clientConfig.caBundle -}}{{- end -}}
+{{- end -}}
+{{- end -}}
 apiVersion: admissionregistration.k8s.io/v1
 kind: MutatingWebhookConfiguration
 metadata:
@@ -10,7 +20,7 @@ metadata:
   {{- end }}
 webhooks:
   - clientConfig:
-      caBundle: Cg==
+      caBundle: {{ default "Cg==" (get $vals "apps") }}
       service:
         name: {{ template "kubevela.name" . }}-webhook
         namespace: {{ .Release.Namespace }}
@@ -36,7 +46,7 @@ webhooks:
         resources:
           - applications
   - clientConfig:
-      caBundle: Cg==
+      caBundle: {{ default "Cg==" (get $vals "comps") }}
       service:
         name: {{ template "kubevela.name" . }}-webhook
         namespace: {{ .Release.Namespace }}
diff --git a/charts/vela-core/templates/admission-webhooks/validatingWebhookConfiguration.yaml b/charts/vela-core/templates/admission-webhooks/validatingWebhookConfiguration.yaml
index 270aed33..11ad4bbe 100644
--- a/charts/vela-core/templates/admission-webhooks/validatingWebhookConfiguration.yaml
+++ b/charts/vela-core/templates/admission-webhooks/validatingWebhookConfiguration.yaml
@@ -1,4 +1,16 @@
 {{- if .Values.admissionWebhooks.enabled -}}
+{{- /* Preserve existing caBundle on upgrade to avoid breaking admission if hooks fail. */}}
+{{- $vName := printf "%s-admission" (include "kubevela.fullname" .) -}}
+{{- $existing := (lookup "admissionregistration.k8s.io/v1" "ValidatingWebhookConfiguration" "" $vName) -}}
+{{- $vals := dict "traits" "" "apps" "" "comps" "" "policies" "" -}}
+{{- if $existing -}}
+{{- range $existing.webhooks -}}
+{{- if eq .name "validating.core.oam.dev.v1beta1.traitdefinitions" -}}{{- $_ := set $vals "traits" .clientConfig.caBundle -}}{{- end -}}
+{{- if eq .name "validating.core.oam.dev.v1beta1.applications" -}}{{- $_ := set $vals "apps" .clientConfig.caBundle -}}{{- end -}}
+{{- if eq .name "validating.core.oam-dev.v1beta1.componentdefinitions" -}}{{- $_ := set $vals "comps" .clientConfig.caBundle -}}{{- end -}}
+{{- if eq .name "validating.core.oam-dev.v1beta1.policydefinitions" -}}{{- $_ := set $vals "policies" .clientConfig.caBundle -}}{{- end -}}
+{{- end -}}
+{{- end -}}
 apiVersion: admissionregistration.k8s.io/v1
 kind: ValidatingWebhookConfiguration
 metadata:
@@ -10,7 +22,7 @@ metadata:
   {{- end }}
 webhooks:
   - clientConfig:
-      caBundle: Cg==
+      caBundle: {{ default "Cg==" (get $vals "traits") }}
       service:
         name: {{ template "kubevela.name" . }}-webhook
         namespace: {{ .Release.Namespace }}
@@ -37,7 +49,7 @@ webhooks:
           - traitdefinitions
     timeoutSeconds: 5
   - clientConfig:
-      caBundle: Cg==
+      caBundle: {{ default "Cg==" (get $vals "apps") }}
       service:
         name: {{ template "kubevela.name" . }}-webhook
         namespace: {{ .Release.Namespace }}
@@ -63,7 +75,7 @@ webhooks:
         resources:
           - applications
   - clientConfig:
-      caBundle: Cg==
+      caBundle: {{ default "Cg==" (get $vals "comps") }}
       service:
         name: {{ template "kubevela.name" . }}-webhook
         namespace: {{ .Release.Namespace }}
@@ -89,7 +101,7 @@ webhooks:
         resources:
           - componentdefinitions
   - clientConfig:
-      caBundle: Cg==
+      caBundle: {{ default "Cg==" (get $vals "policies") }}
       service:
         name: {{ template "kubevela.name" . }}-webhook
         namespace: {{ .Release.Namespace }}
@@ -114,4 +126,30 @@ webhooks:
           - UPDATE
         resources:
           - policydefinitions
+  - clientConfig:
+      caBundle: Cg==
+      service:
+        name: {{ template "kubevela.name" . }}-webhook
+        namespace: {{ .Release.Namespace }}
+        path: /validating-core-oam-dev-v1beta1-workflowstepdefinitions
+    {{- if .Values.admissionWebhooks.patch.enabled  }}
+    failurePolicy: Ignore
+    {{- else }}
+    failurePolicy: Fail
+    {{- end }}
+    name: validating.core.oam-dev.v1beta1.workflowstepdefinitions
+    sideEffects: None
+    admissionReviewVersions:
+      - v1beta1
+      - v1
+    rules:
+      - apiGroups:
+          - core.oam.dev
+        apiVersions:
+          - v1beta1
+        operations:
+          - CREATE
+          - UPDATE
+        resources:
+          - workflowstepdefinitions
 {{- end -}}
diff --git a/charts/vela-core/templates/defwithtemplate/vela-cli.yaml b/charts/vela-core/templates/defwithtemplate/vela-cli.yaml
index a1f349ca..e69ecacc 100644
--- a/charts/vela-core/templates/defwithtemplate/vela-cli.yaml
+++ b/charts/vela-core/templates/defwithtemplate/vela-cli.yaml
@@ -126,8 +126,6 @@ spec:
         }
 
         parameter: {
-        	// +usage=Specify the name of the addon.
-        	addonName: string
         	// +usage=Specify the vela command
         	command: [...string]
         	// +usage=Specify the image
diff --git a/charts/vela-core/templates/kubevela-controller.yaml b/charts/vela-core/templates/kubevela-controller.yaml
index 80bf8028..746ba142 100644
--- a/charts/vela-core/templates/kubevela-controller.yaml
+++ b/charts/vela-core/templates/kubevela-controller.yaml
@@ -313,8 +313,9 @@ spec:
             - "--feature-gates=DisableWorkflowContextConfigMapCache={{- .Values.featureGates.disableWorkflowContextConfigMapCache | toString -}}"
             - "--feature-gates=EnableCueValidation={{- .Values.featureGates.enableCueValidation | toString -}}"
             - "--feature-gates=EnableApplicationStatusMetrics={{- .Values.featureGates.enableApplicationStatusMetrics | toString -}}"
+            - "--feature-gates=ValidateResourcesExist={{- .Values.featureGates.validateResourcesExist | toString -}}"
+            - "--feature-gates=ValidateDefinitionPermissions={{ .Values.authorization.definitionValidationEnabled | toString -}}"
             {{ if .Values.authentication.enabled }}
-            - "--feature-gates=ValidateDefinitionPermissions={{ .Values.authentication.definitions.enabled | toString -}}"
             {{ if .Values.authentication.withUser }}
             - "--authentication-with-user"
             {{ end }}
@@ -327,6 +328,7 @@ spec:
             - "--feature-gates=ValidateComponentWhenSharding={{- .Values.featureGates.validateComponentWhenSharding | toString -}}"
             - "--feature-gates=DisableWebhookAutoSchedule={{- .Values.featureGates.disableWebhookAutoSchedule | toString -}}"
             {{ end }}
+            - "--dev-logs={{ .Values.devLogs }}"
           image: {{ .Values.imageRegistry }}{{ .Values.image.repository }}:{{ .Values.image.tag }}
           imagePullPolicy: {{ quote .Values.image.pullPolicy }}
           resources:
diff --git a/charts/vela-core/values.yaml b/charts/vela-core/values.yaml
index 2fa9592e..8b2d5fdb 100644
--- a/charts/vela-core/values.yaml
+++ b/charts/vela-core/values.yaml
@@ -125,6 +125,7 @@ optimize:
 ##@param featureGates.disableWorkflowContextConfigMapCache disable the workflow context's configmap informer cache
 ##@param featureGates.enableCueValidation enable the strict cue validation for cue required parameter fields
 ##@param featureGates.enableApplicationStatusMetrics enable application status metrics and structured logging
+##@param featureGates.validateResourcesExist enable webhook validation to check if resource types referenced in definition templates exist in the cluster
 ##@param
 featureGates:
   gzipResourceTracker: false
@@ -142,6 +143,7 @@ featureGates:
   disableWorkflowContextConfigMapCache: true
   enableCueValidation: false
   enableApplicationStatusMetrics: false
+  validateResourcesExist: false
 
 ## @section MultiCluster parameters
 
@@ -256,6 +258,9 @@ rbac:
 ## @param logDebug Enable debug logs for development purpose
 logDebug: false
 
+## @param devLogs Enable formatted logging support for development purpose
+devLogs: false
+
 ## @param logFilePath If non-empty, write log files in this path
 logFilePath: ""
 
@@ -290,18 +295,27 @@ kubeClient:
   burst: 600
 
 ## @param authentication.enabled Enable authentication framework for applications
-## @param authentication.definitions.enabled Enable definition permission validation (requires authentication.enabled)  
-## @param authentication.withUser Application authentication will impersonate as the request User
-## @param authentication.defaultUser Application authentication will impersonate as the User if no user provided in Application
+## SECURITY NOTE: When enabled WITH authentication.withUser=true, KubeVela impersonates the requesting user
+## when deploying resources, ensuring that users cannot deploy resources beyond their RBAC permissions.
+## This is strongly recommended when KubeVela has cluster-admin or other high-level permissions.
+## @param authentication.withUser Application authentication will impersonate as the request User (must be true for security)
+## @param authentication.defaultUser Application authentication will impersonate as the User if no user provided or withUser is false
 ## @param authentication.groupPattern Application authentication will impersonate as the request Group that matches the pattern
 authentication:
   enabled: false
-  definitions:
-    enabled: false
   withUser: true
   defaultUser: kubevela:vela-core
   groupPattern: kubevela:*
 
+## @param authorization.definitionValidationEnabled Enable definition permission validation for RBAC checks on definitions
+## SECURITY NOTE: If KubeVela is running with cluster-admin or high-level permissions,
+## consider enabling this feature and/or authentication.enabled for security:
+## - This feature: Validates users can only reference definitions they have RBAC access to
+## - authentication.enabled: Makes KubeVela impersonate users, applying their full RBAC permissions
+## Both features can be used together for defense in depth.
+authorization:
+  definitionValidationEnabled: false
+
 ## @param sharding.enabled When sharding enabled, the controller will run as master mode. Refer to https://github.com/kubevela/kubevela/blob/master/design/vela-core/sharding.md for details.
 ## @param sharding.schedulableShards The shards available for scheduling. If empty, dynamic discovery will be used.
 sharding:
diff --git a/cmd/core/app/color_writer.go b/cmd/core/app/color_writer.go
new file mode 100644
index 00000000..dd1fc51a
--- /dev/null
+++ b/cmd/core/app/color_writer.go
@@ -0,0 +1,580 @@
+/*
+Copyright 2022 The KubeVela Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package app
+
+import (
+	"bytes"
+	"fmt"
+	"go/ast"
+	"go/parser"
+	"go/token"
+	"io"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strconv"
+	"strings"
+	"sync"
+	"unicode"
+)
+
+const (
+	ansiReset    = "\x1b[0m"
+	ansiDate     = "\x1b[36m"
+	ansiInfo     = "\x1b[32m"
+	ansiWarn     = "\x1b[33m"
+	ansiError    = "\x1b[31m"
+	ansiFatal    = "\x1b[35m"
+	ansiDebug    = "\x1b[34m"
+	ansiThread   = "\x1b[34m"
+	ansiLocation = "\x1b[93m"
+	ansiMessage  = "\x1b[97m"
+	ansiKey      = "\x1b[96m"
+	ansiValue    = "\x1b[37m"
+)
+
+var methodPatterns = []*regexp.Regexp{
+	regexp.MustCompile(`(?i)\b(?:caller|func|function|method)\s*[:=]\s*"?([a-zA-Z0-9_./()*-]+)`),
+}
+
+var srcIdx = newSourceIndex()
+
+type colorWriter struct {
+	dst io.Writer
+	mu  sync.Mutex
+	buf bytes.Buffer
+}
+
+func newColorWriter(dst io.Writer) io.Writer {
+	return &colorWriter{dst: dst}
+}
+
+func (w *colorWriter) Write(p []byte) (int, error) {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	written := 0
+	for len(p) > 0 {
+		idx := bytes.IndexByte(p, '\n')
+		if idx == -1 {
+			_, _ = w.buf.Write(p)
+			written += len(p)
+			break
+		}
+		_, _ = w.buf.Write(p[:idx])
+		if err := w.flushLineLocked(); err != nil {
+			written += idx
+			return written, err
+		}
+		if _, err := w.dst.Write([]byte{'\n'}); err != nil {
+			written += idx + 1
+			return written, err
+		}
+		p = p[idx+1:]
+		written += idx + 1
+	}
+	return written, nil
+}
+
+func (w *colorWriter) flushLineLocked() error {
+	if w.buf.Len() == 0 {
+		return nil
+	}
+	line := w.buf.String()
+	w.buf.Reset()
+	_, err := io.WriteString(w.dst, formatColoredLine(line))
+	return err
+}
+
+func formatColoredLine(line string) string {
+	if len(line) == 0 {
+		return line
+	}
+	severity := line[0]
+	rest := line[1:]
+	headerEnd := strings.IndexByte(rest, ']')
+	if headerEnd == -1 {
+		return line
+	}
+	header := rest[:headerEnd]
+	remainder := strings.TrimSpace(rest[headerEnd+1:])
+	fields := strings.Fields(header)
+	if len(fields) < 4 {
+		return line
+	}
+
+	date := fields[0]
+	ts := fields[1]
+	pid := strings.TrimSpace(fields[2])
+	rawLocation := strings.Join(fields[3:], " ")
+	level, levelColor := mapSeverity(severity)
+	location := buildLocation(rawLocation, remainder)
+
+	sb := strings.Builder{}
+	sb.Grow(len(line) + 32)
+
+	sb.WriteString(ansiDate)
+	sb.WriteString("[")
+	sb.WriteString(formatTimestamp(date, ts))
+	sb.WriteString("]")
+	sb.WriteString(ansiReset)
+	sb.WriteString(" ")
+
+	sb.WriteString(levelColor)
+	sb.WriteString("[")
+	sb.WriteString(fmt.Sprintf("%-5s", level))
+	sb.WriteString("]")
+	sb.WriteString(ansiReset)
+	sb.WriteString(" ")
+
+	sb.WriteString(ansiThread)
+	sb.WriteString("[")
+	sb.WriteString(pid)
+	sb.WriteString("]")
+	sb.WriteString(ansiReset)
+	sb.WriteString(" ")
+
+	sb.WriteString(ansiLocation)
+	sb.WriteString("[")
+	sb.WriteString(location)
+	sb.WriteString("]")
+	sb.WriteString(ansiReset)
+
+	if remainder != "" {
+		msg, fields := splitMessageAndFields(remainder)
+		if msg != "" {
+			sb.WriteString(" ")
+			sb.WriteString(ansiMessage)
+			sb.WriteString(msg)
+			sb.WriteString(ansiReset)
+		}
+		for _, field := range fields {
+			sb.WriteString(" ")
+			sb.WriteString(ansiKey)
+			sb.WriteString(field.key)
+			sb.WriteString(ansiReset)
+			sb.WriteString("=")
+			sb.WriteString(ansiValue)
+			sb.WriteString(field.renderValue())
+			sb.WriteString(ansiReset)
+		}
+	}
+
+	return sb.String()
+}
+
+func mapSeverity(s byte) (string, string) {
+	switch s {
+	case 'I':
+		return "INFO", ansiInfo
+	case 'W':
+		return "WARN", ansiWarn
+	case 'E':
+		return "ERROR", ansiError
+	case 'F':
+		return "FATAL", ansiFatal
+	case 'D':
+		return "DEBUG", ansiDebug
+	default:
+		return string(s), ansiDebug
+	}
+}
+
+func formatTimestamp(date, ts string) string {
+	if len(date) == 4 {
+		return fmt.Sprintf("%s-%s %s", date[:2], date[2:], trimMicros(ts))
+	}
+	return strings.TrimSpace(date + " " + trimMicros(ts))
+}
+
+func trimMicros(ts string) string {
+	if dot := strings.IndexByte(ts, '.'); dot != -1 && len(ts) > dot+4 {
+		return ts[:dot+4]
+	}
+	return ts
+}
+
+func buildLocation(rawLocation, remainder string) string {
+	file, line := splitFileAndLine(rawLocation)
+	method := lookupMethod(file, line)
+	if method == "" {
+		method = extractMethodName(remainder)
+	}
+	if file == "" {
+		return rawLocation
+	}
+	if method == "" {
+		if line != "" {
+			return fmt.Sprintf("%s:%s", file, line)
+		}
+		return file
+	}
+	if line == "" {
+		return fmt.Sprintf("%s:%s", file, method)
+	}
+	return fmt.Sprintf("%s:%s:%s", file, method, line)
+}
+
+func splitFileAndLine(raw string) (string, string) {
+	trimmed := strings.TrimSpace(raw)
+	if trimmed == "" {
+		return "", ""
+	}
+	lastColon := strings.LastIndex(trimmed, ":")
+	if lastColon == -1 {
+		return trimmed, ""
+	}
+	return strings.TrimSpace(trimmed[:lastColon]), strings.TrimSpace(trimmed[lastColon+1:])
+}
+
+type kvField struct {
+	key    string
+	value  string
+	quoted bool
+}
+
+func (f kvField) renderValue() string {
+	if f.quoted {
+		return fmt.Sprintf("\"%s\"", f.value)
+	}
+	return f.value
+}
+
+func splitMessageAndFields(s string) (string, []kvField) {
+	s = strings.TrimSpace(s)
+	if s == "" {
+		return "", nil
+	}
+	idx := findFirstKVIndex(s)
+	if idx == -1 {
+		return s, nil
+	}
+	msg := strings.TrimSpace(s[:idx])
+	fields := parseKeyValues(s[idx:])
+	return msg, fields
+}
+
+func findFirstKVIndex(s string) int {
+	inQuotes := false
+	for i := 0; i < len(s); i++ {
+		ch := s[i]
+		if ch == '"' {
+			if i == 0 || s[i-1] != '\\' {
+				inQuotes = !inQuotes
+			}
+			continue
+		}
+		if inQuotes {
+			continue
+		}
+		if ch == '=' {
+			keyEnd := i
+			keyStart := keyEnd - 1
+			for keyStart >= 0 && !unicode.IsSpace(rune(s[keyStart])) {
+				keyStart--
+			}
+			key := strings.TrimSpace(s[keyStart+1 : keyEnd])
+			if isValidKey(key) {
+				return keyStart + 1
+			}
+		}
+	}
+	return -1
+}
+
+func parseKeyValues(s string) []kvField {
+	fields := make([]kvField, 0)
+	i := 0
+	for i < len(s) {
+		for i < len(s) && unicode.IsSpace(rune(s[i])) {
+			i++
+		}
+		if i >= len(s) {
+			break
+		}
+		keyStart := i
+		for i < len(s) && s[i] != '=' && !unicode.IsSpace(rune(s[i])) {
+			i++
+		}
+		keyEnd := i
+		for keyEnd > keyStart && unicode.IsSpace(rune(s[keyEnd-1])) {
+			keyEnd--
+		}
+		if i >= len(s) || s[i] != '=' {
+			for i < len(s) && !unicode.IsSpace(rune(s[i])) {
+				i++
+			}
+			continue
+		}
+		key := s[keyStart:keyEnd]
+		if !isValidKey(key) {
+			i++
+			continue
+		}
+		i++
+		for i < len(s) && unicode.IsSpace(rune(s[i])) {
+			i++
+		}
+		if i >= len(s) {
+			fields = append(fields, kvField{key: key, value: "", quoted: false})
+			break
+		}
+		quoted := false
+		var value string
+		if s[i] == '"' {
+			quoted = true
+			i++
+			start := i
+			for i < len(s) {
+				if s[i] == '"' && (i == start || s[i-1] != '\\') {
+					break
+				}
+				i++
+			}
+			value = s[start:i]
+			if i < len(s) && s[i] == '"' {
+				i++
+			}
+		} else {
+			start := i
+			for i < len(s) && !unicode.IsSpace(rune(s[i])) {
+				i++
+			}
+			value = s[start:i]
+		}
+		fields = append(fields, kvField{key: key, value: value, quoted: quoted})
+	}
+	return fields
+}
+
+func isValidKey(key string) bool {
+	if key == "" {
+		return false
+	}
+	for _, r := range key {
+		if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '-' || r == '.' {
+			continue
+		}
+		return false
+	}
+	return true
+}
+
+func extractMethodName(remainder string) string {
+	if remainder == "" {
+		return ""
+	}
+	for _, pattern := range methodPatterns {
+		if matches := pattern.FindStringSubmatch(remainder); len(matches) > 1 {
+			return simplifyMethodName(matches[1])
+		}
+	}
+	return ""
+}
+
+func simplifyMethodName(name string) string {
+	if name == "" {
+		return ""
+	}
+	name = strings.Trim(name, "\"' ")
+	if idx := strings.LastIndex(name, "/"); idx != -1 {
+		name = name[idx+1:]
+	}
+	return name
+}
+
+func lookupMethod(file, line string) string {
+	if file == "" || line == "" {
+		return ""
+	}
+	ln, err := strconv.Atoi(line)
+	if err != nil || ln <= 0 {
+		return ""
+	}
+	return srcIdx.functionAt(file, ln)
+}
+
+type funcInfo struct {
+	name      string
+	startLine int
+	endLine   int
+}
+
+type fileCache struct {
+	once  sync.Once
+	funcs []funcInfo
+}
+
+type sourceIndex struct {
+	mu    sync.Mutex
+	files map[string]*fileCache
+}
+
+func newSourceIndex() *sourceIndex {
+	return &sourceIndex{files: make(map[string]*fileCache)}
+}
+
+func (s *sourceIndex) functionAt(base string, line int) string {
+	s.mu.Lock()
+	cache, ok := s.files[base]
+	if !ok {
+		cache = &fileCache{}
+		s.files[base] = cache
+	}
+	s.mu.Unlock()
+
+	cache.once.Do(func() {
+		cache.funcs = loadFunctionsFor(base)
+	})
+	for _, fn := range cache.funcs {
+		if line >= fn.startLine && line <= fn.endLine {
+			return fn.name
+		}
+	}
+	return ""
+}
+
+func loadFunctionsFor(base string) []funcInfo {
+	paths := findSourceFiles(base)
+	if len(paths) == 0 {
+		return nil
+	}
+	infos := make([]funcInfo, 0, len(paths)*4)
+	for _, path := range paths {
+		fset := token.NewFileSet()
+		fileAst, err := parser.ParseFile(fset, path, nil, 0)
+		if err != nil {
+			continue
+		}
+		for _, decl := range fileAst.Decls {
+			fn, ok := decl.(*ast.FuncDecl)
+			if !ok || fn.Name == nil {
+				continue
+			}
+			start := fset.Position(fn.Pos()).Line
+			end := fset.Position(fn.End()).Line
+			name := fn.Name.Name
+			if recv := receiverType(fn); recv != "" {
+				name = fmt.Sprintf("%s.%s", recv, name)
+			}
+			infos = append(infos, funcInfo{name: name, startLine: start, endLine: end})
+		}
+	}
+	return infos
+}
+
+func receiverType(fn *ast.FuncDecl) string {
+	if fn.Recv == nil || len(fn.Recv.List) == 0 {
+		return ""
+	}
+	return typeString(fn.Recv.List[0].Type)
+}
+
+func typeString(expr ast.Expr) string {
+	switch v := expr.(type) {
+	case *ast.Ident:
+		return v.Name
+	case *ast.StarExpr:
+		return typeString(v.X)
+	case *ast.SelectorExpr:
+		return v.Sel.Name
+	case *ast.IndexExpr:
+		return typeString(v.X)
+	case *ast.IndexListExpr:
+		return typeString(v.X)
+	case *ast.ParenExpr:
+		return typeString(v.X)
+	default:
+		return ""
+	}
+}
+
+var (
+	searchRootsOnce sync.Once
+	cachedRoots     []string
+)
+
+func findSourceFiles(base string) []string {
+	matches := make([]string, 0)
+	seen := make(map[string]struct{})
+	for _, root := range sourceRoots() {
+		if root == "" {
+			continue
+		}
+		lookupFilesInRoot(root, base, seen, &matches)
+	}
+	return matches
+}
+
+func sourceRoots() []string {
+	searchRootsOnce.Do(func() {
+		roots := make([]string, 0, 4)
+		if env := os.Getenv("VELA_SOURCE_ROOT"); env != "" {
+			for _, item := range strings.Split(env, string(os.PathListSeparator)) {
+				if trimmed := strings.TrimSpace(item); trimmed != "" {
+					roots = append(roots, trimmed)
+				}
+			}
+		}
+		if cwd, err := os.Getwd(); err == nil {
+			roots = append(roots, cwd)
+		}
+		cachedRoots = dedupeStrings(roots)
+	})
+	return cachedRoots
+}
+
+func lookupFilesInRoot(root, base string, seen map[string]struct{}, matches *[]string) {
+	_ = filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
+		if err != nil {
+			// Continue scanning on error.
+			//nolint:nilerr
+			return nil
+		}
+		if d.IsDir() {
+			name := d.Name()
+			if name == "vendor" || name == "node_modules" || strings.HasPrefix(name, ".") {
+				if root == path {
+					return nil
+				}
+				return filepath.SkipDir
+			}
+			return nil
+		}
+		if d.Name() == base {
+			if _, ok := seen[path]; !ok {
+				seen[path] = struct{}{}
+				*matches = append(*matches, path)
+			}
+		}
+		return nil
+	})
+}
+
+func dedupeStrings(in []string) []string {
+	seen := make(map[string]struct{}, len(in))
+	out := make([]string, 0, len(in))
+	for _, v := range in {
+		if _, ok := seen[v]; ok {
+			continue
+		}
+		seen[v] = struct{}{}
+		out = append(out, v)
+	}
+	return out
+}
diff --git a/cmd/core/app/options/options.go b/cmd/core/app/options/options.go
index be3096d3..7959c0f8 100644
--- a/cmd/core/app/options/options.go
+++ b/cmd/core/app/options/options.go
@@ -50,6 +50,7 @@ type CoreOptions struct {
 	LogFilePath             string
 	LogFileMaxSize          uint64
 	LogDebug                bool
+	DevLogs                 bool
 	ControllerArgs          *oamcontroller.Args
 	HealthAddr              string
 	StorageDriver           string
@@ -76,6 +77,7 @@ func NewCoreOptions() *CoreOptions {
 		LogFilePath:             "",
 		LogFileMaxSize:          1024,
 		LogDebug:                false,
+		DevLogs:                 false,
 		ControllerArgs: &oamcontroller.Args{
 			RevisionLimit:                                50,
 			AppRevisionLimit:                             10,
@@ -116,6 +118,7 @@ func (s *CoreOptions) Flags() cliflag.NamedFlagSets {
 	gfs.StringVar(&s.LogFilePath, "log-file-path", s.LogFilePath, "The file to write logs to.")
 	gfs.Uint64Var(&s.LogFileMaxSize, "log-file-max-size", s.LogFileMaxSize, "Defines the maximum size a log file can grow to, Unit is megabytes.")
 	gfs.BoolVar(&s.LogDebug, "log-debug", s.LogDebug, "Enable debug logs for development purpose")
+	gfs.BoolVar(&s.DevLogs, "dev-logs", s.DevLogs, "Enable ANSI color formatting for console logs (ignored when log-file-path is set)")
 	gfs.StringVar(&s.HealthAddr, "health-addr", s.HealthAddr, "The address the health endpoint binds to.")
 	gfs.DurationVar(&s.InformerSyncPeriod, "informer-sync-period", s.InformerSyncPeriod,
 		"The re-sync period for informer in controller-runtime. This is a system-level configuration.")
diff --git a/cmd/core/app/server.go b/cmd/core/app/server.go
index 06482191..36eef859 100644
--- a/cmd/core/app/server.go
+++ b/cmd/core/app/server.go
@@ -103,6 +103,17 @@ func run(ctx context.Context, s *options.CoreOptions) error {
 	restConfig.QPS = float32(s.QPS)
 	restConfig.Burst = s.Burst
 	restConfig.Wrap(auth.NewImpersonatingRoundTripper)
+
+	// Set logger (use --dev-logs=true for local development)
+	if s.DevLogs {
+		logOutput := newColorWriter(os.Stdout)
+		klog.LogToStderr(false)
+		klog.SetOutput(logOutput)
+		ctrl.SetLogger(textlogger.NewLogger(textlogger.NewConfig(textlogger.Output(logOutput))))
+	} else {
+		ctrl.SetLogger(textlogger.NewLogger(textlogger.NewConfig()))
+	}
+
 	klog.InfoS("Kubernetes Config Loaded",
 		"UserAgent", restConfig.UserAgent,
 		"QPS", restConfig.QPS,
@@ -127,8 +138,6 @@ func run(ctx context.Context, s *options.CoreOptions) error {
 		}
 	}
 
-	ctrl.SetLogger(textlogger.NewLogger(textlogger.NewConfig()))
-
 	if utilfeature.DefaultMutableFeatureGate.Enabled(features.ApplyOnce) {
 		commonconfig.ApplicationReSyncPeriod = s.InformerSyncPeriod
 	}
diff --git a/docs/WEBHOOK_DEBUGGING.md b/docs/WEBHOOK_DEBUGGING.md
new file mode 100644
index 00000000..ad544c38
--- /dev/null
+++ b/docs/WEBHOOK_DEBUGGING.md
@@ -0,0 +1,191 @@
+# KubeVela Webhook Debugging Guide
+
+This guide explains how to debug KubeVela webhook validation locally, particularly for the feature that validates ComponentDefinitions, TraitDefinitions, and PolicyDefinitions to ensure they don't reference non-existent CRDs.
+
+## Overview
+
+The webhook validation feature checks that CUE templates in definitions only reference Kubernetes resources that exist on the cluster. This prevents runtime errors when non-existent CRDs are referenced.
+
+## Prerequisites
+
+- Docker Desktop or similar container runtime
+- k3d for local Kubernetes clusters
+- VS Code with Go extension
+- kubectl configured
+- openssl for certificate generation
+
+## Quick Start
+
+```bash
+# 1. Complete setup (cluster + CRDs + webhook)
+make webhook-debug-setup
+
+# 2. Start VS Code debugger
+# Press F5 and select "Debug Webhook Validation"
+```
+
+## Detailed Setup Steps
+
+### 1. Environment Setup
+
+```bash
+# Create k3d cluster
+make k3d-create
+
+# Install KubeVela CRDs
+make manifests
+kubectl apply -f charts/vela-core/crds/
+```
+
+### 2. Webhook Certificate Setup
+
+The webhook requires TLS certificates with proper Subject Alternative Names (SANs) for IP addresses.
+
+```bash
+# Generate certificates and create Kubernetes secret
+make webhook-setup
+```
+
+This creates:
+- CA certificate and key
+- Server certificate with IP SANs (127.0.0.1, Docker internal IP, local machine IP)
+- Kubernetes Secret `webhook-server-cert` in `vela-system` namespace
+- ValidatingWebhookConfiguration pointing to local debugger
+
+### 3. Start Debugger in VS Code
+
+#### VS Code Launch Configuration
+
+If you're using VSCode add this configuration to `.vscode/launch.json`:
+
+```json
+{
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Debug Webhook Validation",
+            "type": "go",
+            "request": "launch",
+            "mode": "debug",
+            "program": "${workspaceFolder}/cmd/core",
+            "args": [
+                "--log-debug=true",
+                "--metrics-addr=:8080",
+                "--enable-leader-election=false",
+                "--use-webhook=true",
+                "--webhook-port=9445",
+                "--webhook-cert-dir=${workspaceFolder}/k8s-webhook-server/serving-certs"
+            ],
+            "env": {
+                "KUBECONFIG": "${env:HOME}/.kube/config",
+                "POD_NAMESPACE": "vela-system"
+            },
+            "showLog": false,
+            "console": "integratedTerminal"
+        }
+    ]
+}
+```
+
+#### Set Breakpoints
+
+Recommended breakpoint locations:
+- `pkg/webhook/core.oam.dev/v1beta1/componentdefinition/validating_handler.go` - ComponentDefinition handler
+- `pkg/webhook/core.oam.dev/v1beta1/traitdefinition/validating_handler.go` - TraitDefinition handler
+- `pkg/webhook/core.oam.dev/v1beta1/policydefinition/validating_handler.go` - PolicyDefinition handler
+- `pkg/webhook/core.oam.dev/v1beta1/workflowstepdefinition/workflowstep_validating_handler.go` - WorkflowDefinition handler
+
+#### Launch Debugger
+
+1. Open VS Code
+2. Press `F5` or go to Run → Start Debugging
+3. Select **"Debug Webhook Validation"** configuration
+4. Wait for webhook server to start (look for message about port 9445)
+
+The debugger configuration includes:
+- `--use-webhook=true` - Enables webhook server
+- `--webhook-port=9445` - Port for webhook server
+- `--webhook-cert-dir` - Path to certificates
+- `POD_NAMESPACE=vela-system` - Required for finding the secret
+
+## Make Targets Reference
+
+| Target | Description |
+|--------|-------------|
+| `make webhook-help` | Show webhook debugging help |
+| `make webhook-debug-setup` | Complete setup (cluster + CRDs + webhook) |
+| `make k3d-create` | Create k3d cluster |
+| `make k3d-delete` | Delete k3d cluster |
+| `make webhook-setup` | Setup certificates and webhook configuration |
+| `make webhook-clean` | Clean up webhook environment |
+
+## Troubleshooting
+
+### Connection Refused Error
+
+If you get "connection refused" errors:
+1. Ensure the debugger is running in VS Code
+2. Check that port 9445 is not blocked by firewall
+3. Verify the webhook server started (check VS Code console)
+
+### TLS Certificate Errors
+
+If you get certificate validation errors:
+1. Regenerate certificates: `make webhook-setup`
+2. Restart the debugger
+3. Ensure IP addresses in certificates match your setup
+
+### Webhook Not Triggering
+
+If the webhook doesn't trigger:
+1. Check ValidatingWebhookConfiguration: `kubectl get validatingwebhookconfiguration`
+2. Verify the webhook URL matches your debugger's IP
+3. Check namespace is correct (vela-system)
+
+### Secret Not Found
+
+If you see "Wait webhook secret" messages:
+1. Ensure the secret exists: `kubectl get secret webhook-server-cert -n vela-system`
+2. Recreate if needed: `make webhook-setup`
+
+## How It Works
+
+1. **Certificate Generation**: Creates TLS certificates with proper SANs for local IPs
+2. **Secret Creation**: Stores certificates in Kubernetes secret
+3. **Webhook Configuration**: Creates ValidatingWebhookConfiguration pointing to local debugger
+4. **Debugger Startup**: VS Code starts the webhook server on port 9445
+5. **Validation**: When definitions are applied, Kubernetes calls the webhook
+6. **Debugging**: Breakpoints allow stepping through validation logic
+
+## Files and Components
+
+- **Script**: `hack/debug-webhook-setup.sh` - Main setup script
+- **Makefile**: `makefiles/develop.mk` - Make targets for debugging
+- **VS Code Config**: `.vscode/launch.json` - Debugger configuration
+- **Test Files**: `test/webhook-*.yaml` - Test manifests
+- **Validation Logic**: `pkg/webhook/utils/utils.go` - Core validation implementation
+- **Handlers**: `pkg/webhook/core.oam.dev/v1beta1/*/validating_handler.go` - Resource handlers
+
+## Clean Up
+
+```bash
+# Clean up webhook setup
+make webhook-clean
+
+# Delete k3d cluster
+make k3d-delete
+```
+
+## Tips
+
+1. **Always start the debugger before testing** - The webhook configuration points to your local machine
+2. **Use breakpoints wisely** - Too many breakpoints can cause timeouts
+3. **Check logs** - VS Code Debug Console shows detailed logs
+4. **Test both valid and invalid cases** - Ensures validation works correctly
+5. **Keep certificates updated** - Regenerate if IPs change
+
+## Related Documentation
+
+- [KubeVela Webhook Implementation](../pkg/webhook/README.md)
+- [CUE Template Validation](../pkg/webhook/utils/README.md)
+- [Admission Webhooks](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/)
\ No newline at end of file
diff --git a/e2e/addon/mock/testrepo/helm-repo/index.yaml b/e2e/addon/mock/testrepo/helm-repo/index.yaml
index fc089d7d..87ee524c 100644
--- a/e2e/addon/mock/testrepo/helm-repo/index.yaml
+++ b/e2e/addon/mock/testrepo/helm-repo/index.yaml
@@ -32,8 +32,8 @@ entries:
       icon: https://static.kubevela.net/images/logos/KubeVela%20-03.png
       name: vela-workflow
       urls:
-        - http://127.0.0.1:9098/helm/vela-workflow-v0.3.5.tgz
-      version: v0.3.5
+        - http://127.0.0.1:9098/helm/vela-workflow-v0.6.2.tgz
+      version: v0.6.2
   foo:
     - created: "2022-10-29T09:11:16.865230605Z"
       description: Vela test addon named foo
diff --git a/e2e/addon/mock/testrepo/helm-repo/vela-workflow-v0.3.5.tgz b/e2e/addon/mock/testrepo/helm-repo/vela-workflow-v0.3.5.tgz
deleted file mode 100644
index 0850ee9e..00000000
Binary files a/e2e/addon/mock/testrepo/helm-repo/vela-workflow-v0.3.5.tgz and /dev/null differ
diff --git a/e2e/addon/mock/testrepo/helm-repo/vela-workflow-v0.6.2.tgz b/e2e/addon/mock/testrepo/helm-repo/vela-workflow-v0.6.2.tgz
new file mode 100644
index 00000000..a017b014
Binary files /dev/null and b/e2e/addon/mock/testrepo/helm-repo/vela-workflow-v0.6.2.tgz differ
diff --git a/e2e/addon/mock/vela_addon_mock_server.go b/e2e/addon/mock/vela_addon_mock_server.go
index 31cb8098..279d99e0 100644
--- a/e2e/addon/mock/vela_addon_mock_server.go
+++ b/e2e/addon/mock/vela_addon_mock_server.go
@@ -125,8 +125,8 @@ var helmHandler http.HandlerFunc = func(rw http.ResponseWriter, req *http.Reques
 			_, _ = rw.Write([]byte(err.Error()))
 		}
 		rw.Write(file)
-	case strings.Contains(req.URL.Path, "vela-workflow-v0.3.5.tgz"):
-		file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/vela-workflow-v0.3.5.tgz")
+	case strings.Contains(req.URL.Path, "vela-workflow-v0.6.2.tgz"):
+		file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/vela-workflow-v0.6.2.tgz")
 		if err != nil {
 			_, _ = rw.Write([]byte(err.Error()))
 		}
diff --git a/hack/debug-webhook-setup.sh b/hack/debug-webhook-setup.sh
new file mode 100755
index 00000000..54b069ac
--- /dev/null
+++ b/hack/debug-webhook-setup.sh
@@ -0,0 +1,243 @@
+#!/bin/bash
+# Webhook debugging setup script for KubeVela
+# This script sets up everything needed to debug webhooks locally
+
+set -e
+
+# Configuration
+CERT_DIR="k8s-webhook-server/serving-certs"
+NAMESPACE="vela-system"
+SECRET_NAME="webhook-server-cert"
+WEBHOOK_CONFIG_NAME="kubevela-vela-core-admission"
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+echo -e "${GREEN}=== KubeVela Webhook Debug Setup ===${NC}"
+
+# Function to check prerequisites
+check_prerequisites() {
+    echo -e "${YELLOW}Checking prerequisites...${NC}"
+
+    # Check kubectl
+    if ! command -v kubectl &> /dev/null; then
+        echo -e "${RED}kubectl is not installed${NC}"
+        exit 1
+    fi
+
+    # Check openssl
+    if ! command -v openssl &> /dev/null; then
+        echo -e "${RED}openssl is not installed${NC}"
+        exit 1
+    fi
+
+    # Check cluster connectivity
+    if ! kubectl cluster-info &> /dev/null; then
+        echo -e "${RED}Cannot connect to Kubernetes cluster${NC}"
+        echo "Please ensure your kubeconfig is set up correctly"
+        exit 1
+    fi
+
+    # Wait for cluster to be ready
+    echo "Waiting for cluster nodes to be ready..."
+    kubectl wait --for=condition=Ready nodes --all --timeout=60s &> /dev/null || true
+
+    echo -e "${GREEN}Prerequisites check passed${NC}"
+}
+
+# Function to create namespace if not exists
+create_namespace() {
+    echo -e "${YELLOW}Creating namespace ${NAMESPACE}...${NC}"
+    kubectl create namespace ${NAMESPACE} --dry-run=client -o yaml | kubectl apply -f -
+    echo -e "${GREEN}Namespace ready${NC}"
+}
+
+# Function to generate certificates
+generate_certificates() {
+    echo -e "${YELLOW}Generating webhook certificates...${NC}"
+
+    # Create directory
+    mkdir -p ${CERT_DIR}
+
+    # Clean old certificates
+    rm -f ${CERT_DIR}/*
+
+    # Generate CA private key
+    openssl genrsa -out ${CERT_DIR}/ca.key 2048
+
+    # Generate CA certificate
+    openssl req -x509 -new -nodes -key ${CERT_DIR}/ca.key -days 365 -out ${CERT_DIR}/ca.crt \
+        -subj "/CN=webhook-ca"
+
+    # Generate server private key
+    openssl genrsa -out ${CERT_DIR}/tls.key 2048
+
+    # Get host IP for Docker internal network
+    # NOTE: 192.168.5.2 is the standard k3d host gateway IP that allows containers to reach the host machine
+    # This is only for local k3d development environments - DO NOT use this script in production
+    # With failurePolicy: Fail, an unreachable webhook can block CRD operations cluster-wide
+    HOST_IP="192.168.5.2"
+    LOCAL_IP=$(ifconfig | grep "inet " | grep -v 127.0.0.1 | head -1 | awk '{print $2}')
+
+    # Create certificate config with SANs
+    cat > /tmp/webhook.conf << EOF
+[req]
+req_extensions = v3_req
+distinguished_name = req_distinguished_name
+[req_distinguished_name]
+[v3_req]
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+subjectAltName = @alt_names
+[alt_names]
+DNS.1 = localhost
+DNS.2 = vela-webhook.${NAMESPACE}.svc
+DNS.3 = vela-webhook.${NAMESPACE}.svc.cluster.local
+DNS.4 = *.${NAMESPACE}.svc
+DNS.5 = *.${NAMESPACE}.svc.cluster.local
+IP.1 = 127.0.0.1
+IP.2 = ${HOST_IP}
+IP.3 = ${LOCAL_IP}
+EOF
+
+    # Generate certificate request
+    openssl req -new -key ${CERT_DIR}/tls.key -out /tmp/server.csr \
+        -subj "/CN=vela-webhook.${NAMESPACE}.svc" -config /tmp/webhook.conf
+
+    # Generate server certificate with SANs
+    openssl x509 -req -in /tmp/server.csr -CA ${CERT_DIR}/ca.crt -CAkey ${CERT_DIR}/ca.key \
+        -CAcreateserial -out ${CERT_DIR}/tls.crt -days 365 \
+        -extensions v3_req -extfile /tmp/webhook.conf
+
+    echo -e "${GREEN}Certificates generated with IP SANs: 127.0.0.1, ${HOST_IP}, ${LOCAL_IP}${NC}"
+
+    # Clean up temp files
+    rm -f /tmp/server.csr /tmp/webhook.conf
+}
+
+# Function to create Kubernetes secret
+create_k8s_secret() {
+    echo -e "${YELLOW}Creating Kubernetes secret...${NC}"
+
+    # Delete old secret if exists
+    kubectl delete secret ${SECRET_NAME} -n ${NAMESPACE} --ignore-not-found
+
+    # Create new secret
+    kubectl create secret tls ${SECRET_NAME} \
+        --cert=${CERT_DIR}/tls.crt \
+        --key=${CERT_DIR}/tls.key \
+        -n ${NAMESPACE}
+
+    echo -e "${GREEN}Secret ${SECRET_NAME} created in namespace ${NAMESPACE}${NC}"
+}
+
+# Function to create webhook configuration
+create_webhook_config() {
+    echo -e "${YELLOW}Creating webhook configuration...${NC}"
+
+    # Get CA bundle
+    CA_BUNDLE=$(cat ${CERT_DIR}/ca.crt | base64 | tr -d '\n')
+
+    # Delete old webhook configuration if exists
+    kubectl delete validatingwebhookconfiguration ${WEBHOOK_CONFIG_NAME} --ignore-not-found
+
+    # Create webhook configuration
+    cat > /tmp/webhook-config.yaml << EOF
+apiVersion: admissionregistration.k8s.io/v1
+kind: ValidatingWebhookConfiguration
+metadata:
+  name: ${WEBHOOK_CONFIG_NAME}
+webhooks:
+- name: componentdefinition.core.oam.dev
+  clientConfig:
+    url: https://${HOST_IP}:9445/validating-core-oam-dev-v1beta1-componentdefinitions
+    caBundle: ${CA_BUNDLE}
+  rules:
+  - apiGroups: ["core.oam.dev"]
+    apiVersions: ["v1beta1"]
+    resources: ["componentdefinitions"]
+    operations: ["CREATE", "UPDATE"]
+  admissionReviewVersions: ["v1", "v1beta1"]
+  sideEffects: None
+  failurePolicy: Fail
+- name: traitdefinition.core.oam.dev
+  clientConfig:
+    url: https://${HOST_IP}:9445/validating-core-oam-dev-v1beta1-traitdefinitions
+    caBundle: ${CA_BUNDLE}
+  rules:
+  - apiGroups: ["core.oam.dev"]
+    apiVersions: ["v1beta1"]
+    resources: ["traitdefinitions"]
+    operations: ["CREATE", "UPDATE"]
+  admissionReviewVersions: ["v1", "v1beta1"]
+  sideEffects: None
+  failurePolicy: Fail
+- name: policydefinition.core.oam.dev
+  clientConfig:
+    url: https://${HOST_IP}:9445/validating-core-oam-dev-v1beta1-policydefinitions
+    caBundle: ${CA_BUNDLE}
+  rules:
+  - apiGroups: ["core.oam.dev"]
+    apiVersions: ["v1beta1"]
+    resources: ["policydefinitions"]
+    operations: ["CREATE", "UPDATE"]
+  admissionReviewVersions: ["v1", "v1beta1"]
+  sideEffects: None
+  failurePolicy: Fail
+- name: workflowstepdefinition.core.oam.dev
+  clientConfig:
+    url: https://${HOST_IP}:9445/validating-core-oam-dev-v1beta1-workflowstepdefinitions
+    caBundle: ${CA_BUNDLE}
+  rules:
+  - apiGroups: ["core.oam.dev"]
+    apiVersions: ["v1beta1"]
+    resources: ["workflowstepdefinitions"]
+    operations: ["CREATE", "UPDATE"]
+  admissionReviewVersions: ["v1", "v1beta1"]
+  sideEffects: None
+  failurePolicy: Fail
+EOF
+
+    kubectl apply -f /tmp/webhook-config.yaml
+    rm -f /tmp/webhook-config.yaml
+
+    echo -e "${GREEN}Webhook configuration created${NC}"
+}
+
+# Function to show next steps
+show_next_steps() {
+    echo -e "${GREEN}"
+    echo "========================================="
+    echo "Webhook debugging setup complete!"
+    echo "========================================="
+    echo -e "${NC}"
+    echo "Next steps:"
+    echo "1. Open VS Code"
+    echo "2. Set breakpoints in webhook validation code:"
+    echo "   - pkg/webhook/utils/utils.go:141"
+    echo "   - pkg/webhook/core.oam.dev/v1beta1/componentdefinition/validating_handler.go:74"
+    echo "3. Press F5 and select 'Debug Webhook Validation'"
+    echo "4. Wait for webhook server to start (port 9445)"
+    echo "5. Test with kubectl apply commands"
+    echo ""
+    echo -e "${YELLOW}Test command (should be rejected):${NC}"
+    echo 'kubectl apply -f test/webhook-test-invalid.yaml'
+    echo ""
+    echo -e "${GREEN}The webhook will reject ComponentDefinitions with non-existent CRDs${NC}"
+}
+
+# Main execution
+main() {
+    check_prerequisites
+    create_namespace
+    generate_certificates
+    create_k8s_secret
+    create_webhook_config
+    show_next_steps
+}
+
+# Run main function
+main "$@"
\ No newline at end of file
diff --git a/makefiles/develop.mk b/makefiles/develop.mk
index f957b844..585e613a 100644
--- a/makefiles/develop.mk
+++ b/makefiles/develop.mk
@@ -30,3 +30,75 @@ core-run: fmt vet manifests
 .PHONY: gen-cue
 gen-cue:
 	./hack/cuegen/cuegen.sh $(DIR) $(FLAGS)
+
+# ==============================================================================
+# Webhook Debug and Development Targets
+
+K3D_CLUSTER_NAME ?= kubevela-debug
+K3D_VERSION ?= v1.31.5
+
+## webhook-help: Show webhook debugging help
+.PHONY: webhook-help
+webhook-help:
+	@echo "=== KubeVela Webhook Debugging Guide ==="
+	@echo ""
+	@echo "Quick Start (recommended):"
+	@echo "  1. make webhook-debug-setup   # Complete setup"
+	@echo "  2. Start VS Code debugger (F5) with 'Debug Webhook Validation'"
+	@echo ""
+	@echo "Individual Commands:"
+	@echo "  make k3d-create               # Create k3d cluster"
+	@echo "  make k3d-delete               # Delete k3d cluster"
+	@echo "  make webhook-setup            # Setup webhook (certs + config)"
+	@echo "  make webhook-clean            # Clean up webhook setup"
+
+## k3d-create: Create a k3d cluster for debugging
+.PHONY: k3d-create
+k3d-create:
+	@echo "Creating k3d cluster: $(K3D_CLUSTER_NAME)"
+	@if k3d cluster list | grep -q "^$(K3D_CLUSTER_NAME)"; then \
+		echo "k3d cluster $(K3D_CLUSTER_NAME) already exists"; \
+	else \
+		k3d cluster create "$(K3D_CLUSTER_NAME)" \
+			--servers 1 \
+			--agents 1 \
+			--wait || (echo "Failed to create k3d cluster" && exit 1); \
+	fi
+	@kubectl config use-context "k3d-$(K3D_CLUSTER_NAME)"
+	@echo "k3d cluster $(K3D_CLUSTER_NAME) ready"
+
+## k3d-delete: Delete the k3d cluster
+.PHONY: k3d-delete
+k3d-delete:
+	@echo "Deleting k3d cluster: $(K3D_CLUSTER_NAME)"
+	@k3d cluster delete "$(K3D_CLUSTER_NAME)" || true
+
+## webhook-setup: Setup webhook certificates and configuration
+.PHONY: webhook-setup
+webhook-setup:
+	@echo "Setting up webhook certificates and configuration..."
+	@chmod +x hack/debug-webhook-setup.sh
+	@./hack/debug-webhook-setup.sh
+
+## webhook-debug-setup: Complete webhook debug environment setup
+.PHONY: webhook-debug-setup
+webhook-debug-setup:
+	@echo "Setting up complete webhook debug environment..."
+	@$(MAKE) k3d-create
+	@echo "Waiting for cluster to be ready..."
+	@sleep 5
+	@kubectl wait --for=condition=Ready nodes --all --timeout=60s || true
+	@echo "Installing KubeVela CRDs..."
+	@$(MAKE) manifests
+	@kubectl apply -f charts/vela-core/crds/ --validate=false
+	@echo "Setting up webhook..."
+	@$(MAKE) webhook-setup
+
+## webhook-clean: Clean up webhook debug environment
+.PHONY: webhook-clean
+webhook-clean:
+	@echo "Cleaning up webhook debug environment..."
+	@rm -rf k8s-webhook-server/
+	@kubectl delete secret webhook-server-cert -n vela-system --ignore-not-found
+	@kubectl delete validatingwebhookconfiguration kubevela-vela-core-admission --ignore-not-found
+	@echo "Webhook debug environment cleaned"
diff --git a/makefiles/e2e.mk b/makefiles/e2e.mk
index f3b2957d..3ae7d544 100644
--- a/makefiles/e2e.mk
+++ b/makefiles/e2e.mk
@@ -31,6 +31,7 @@ e2e-setup-core-wo-auth:
 		--set multicluster.clusterGateway.image.repository=ghcr.io/oam-dev/cluster-gateway \
 		--set admissionWebhooks.patch.image.repository=ghcr.io/oam-dev/kube-webhook-certgen/kube-webhook-certgen \
 		--set featureGates.enableCueValidation=true \
+		--set featureGates.validateResourcesExist=true \
 	    --wait kubevela ./charts/vela-core          \
 		--debug
 
@@ -48,10 +49,11 @@ e2e-setup-core-w-auth:
 	    ./charts/vela-core                              \
 	    --set authentication.enabled=true               \
 	    --set authentication.withUser=true              \
-	    --set authentication.groupPattern=*             \
+	    --set authentication.groupPattern='*'           \
 	    --set featureGates.zstdResourceTracker=true     \
 	    --set featureGates.zstdApplicationRevision=true \
 	    --set featureGates.validateComponentWhenSharding=true \
+	    --set featureGates.validateResourcesExist=true \
 	    --set multicluster.clusterGateway.enabled=true  \
 			--set multicluster.clusterGateway.image.repository=ghcr.io/oam-dev/cluster-gateway \
 			--set admissionWebhooks.patch.image.repository=ghcr.io/oam-dev/kube-webhook-certgen/kube-webhook-certgen \
@@ -85,6 +87,32 @@ e2e-test:
 	ginkgo -v ./test/e2e-test
 	@$(OK) tests pass
 
+# Run e2e tests with k3d and webhook validation
+.PHONY: e2e-test-local
+e2e-test-local:
+	# Create k3d cluster if needed
+	@k3d cluster create kubevela-debug --servers 1 --agents 1 || true
+	# Build and load image
+	docker build -t vela-core:e2e-test -f Dockerfile . --build-arg=VERSION=e2e-test --build-arg=GITVERSION=test
+	k3d image import vela-core:e2e-test -c kubevela-debug
+	# Deploy with Helm
+	kubectl delete validatingwebhookconfiguration kubevela-vela-core-admission 2>/dev/null || true
+	helm upgrade --install kubevela ./charts/vela-core \
+		--namespace vela-system --create-namespace \
+		--set image.repository=vela-core \
+		--set image.tag=e2e-test \
+		--set image.pullPolicy=IfNotPresent \
+		--set admissionWebhooks.enabled=true \
+		--set featureGates.enableCueValidation=true \
+		--set featureGates.validateResourcesExist=true \
+		--set applicationRevisionLimit=5 \
+		--set controllerArgs.reSyncPeriod=1m \
+		--wait --timeout 3m
+	# Run tests
+	ginkgo -v ./test/e2e-test
+	@$(OK) tests pass
+
+
 .PHONY: e2e-addon-test
 e2e-addon-test:
 	cp bin/vela /tmp/
diff --git a/pkg/addon/reader_gitlab.go b/pkg/addon/reader_gitlab.go
index d2a0bcb1..8fe46399 100644
--- a/pkg/addon/reader_gitlab.go
+++ b/pkg/addon/reader_gitlab.go
@@ -52,6 +52,9 @@ func (g GitLabItem) GetType() string {
 
 // GetPath get addon's sub item path
 func (g GitLabItem) GetPath() string {
+	if g.basePath == "" {
+		return g.path
+	}
 	return g.path[len(g.basePath)+1:]
 }
 
diff --git a/pkg/appfile/dryrun/dryrun.go b/pkg/appfile/dryrun/dryrun.go
index a564bc27..0f3bf174 100644
--- a/pkg/appfile/dryrun/dryrun.go
+++ b/pkg/appfile/dryrun/dryrun.go
@@ -109,12 +109,23 @@ func (d *Option) ValidateApp(ctx context.Context, filename string) error {
 	if err != nil {
 		return err
 	}
-
 	namespace := oamutil.GetDefinitionNamespaceWithCtx(ctx)
-
 	if namespace != "" {
-		app.SetNamespace(namespace)
-	} else if len(app.GetNamespace()) == 0 {
+		// Verify if namespace exists in cluster
+		ns := &corev1.Namespace{}
+		if err := d.Client.Get(ctx, client.ObjectKey{Name: namespace}, ns); err != nil {
+			if client.IgnoreNotFound(err) != nil {
+				// Non-NotFound error (RBAC, connectivity, etc.) - return the error
+				return err
+			}
+			// Namespace doesn't exist, set default
+			app.SetNamespace(corev1.NamespaceDefault)
+		} else {
+			// Namespace exists, use it
+			app.SetNamespace(namespace)
+		}
+	} else {
+		// Namespace is empty, set default
 		app.SetNamespace(corev1.NamespaceDefault)
 	}
 
diff --git a/pkg/controller/core.oam.dev/v1beta1/application/generator.go b/pkg/controller/core.oam.dev/v1beta1/application/generator.go
index ce742ac1..138d5dc5 100644
--- a/pkg/controller/core.oam.dev/v1beta1/application/generator.go
+++ b/pkg/controller/core.oam.dev/v1beta1/application/generator.go
@@ -508,6 +508,7 @@ func getComponentResources(ctx context.Context, manifest *types.ComponentManifes
 	return workload, traits, nil
 }
 
+// generateContextDataFromApp builds the process context for workflow (non-component) execution.
 func generateContextDataFromApp(app *v1beta1.Application, appRev string) velaprocess.ContextData {
 	data := velaprocess.ContextData{
 		Namespace:       app.Namespace,
@@ -519,5 +520,12 @@ func generateContextDataFromApp(app *v1beta1.Application, appRev string) velapro
 		data.WorkflowName = app.Annotations[oam.AnnotationWorkflowName]
 		data.PublishVersion = app.Annotations[oam.AnnotationPublishVersion]
 	}
+	// pass labels and annotations to workflow context
+	if len(app.Labels) > 0 {
+		data.AppLabels = app.Labels
+	}
+	if len(app.Annotations) > 0 {
+		data.AppAnnotations = app.Annotations
+	}
 	return data
 }
diff --git a/pkg/features/controller_features.go b/pkg/features/controller_features.go
index 841cca98..16a8518f 100644
--- a/pkg/features/controller_features.go
+++ b/pkg/features/controller_features.go
@@ -119,6 +119,10 @@ const (
 
 	// EnableApplicationStatusMetrics enable the collection and export of application status metrics and structured logging
 	EnableApplicationStatusMetrics = "EnableApplicationStatusMetrics"
+
+	// ValidateResourcesExist enables webhook validation to check if resource types referenced in
+	// ComponentDefinition/TraitDefinition/WorkflowStepDefinition/PolicyDefinition CUE templates exist in the cluster
+	ValidateResourcesExist = "ValidateResourcesExist"
 )
 
 var defaultFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
@@ -146,6 +150,7 @@ var defaultFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
 	DisableWorkflowContextConfigMapCache:          {Default: true, PreRelease: featuregate.Alpha},
 	EnableCueValidation:                           {Default: false, PreRelease: featuregate.Beta},
 	EnableApplicationStatusMetrics:                {Default: false, PreRelease: featuregate.Alpha},
+	ValidateResourcesExist:                        {Default: false, PreRelease: featuregate.Alpha},
 }
 
 func init() {
diff --git a/pkg/logging/logger.go b/pkg/logging/logger.go
new file mode 100644
index 00000000..73c8d282
--- /dev/null
+++ b/pkg/logging/logger.go
@@ -0,0 +1,173 @@
+/*
+Copyright 2024 The KubeVela Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package logging provides structured logging utilities for KubeVela webhooks
+// with focus on request traceability and observability.
+package logging
+
+import (
+	"context"
+	"time"
+
+	"github.com/go-logr/logr"
+	"sigs.k8s.io/controller-runtime/pkg/log"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+)
+
+// Structured logging field keys - consistent across all handlers for observability
+const (
+	// Core traceability fields
+	FieldRequestID = "requestID"  // Unique identifier for request correlation
+	FieldOperation = "operation"  // Webhook operation (CREATE/UPDATE/DELETE)
+	FieldHandler   = "handler"    // Handler processing the request
+	FieldStep      = "step"       // Current processing step
+	FieldDuration  = "durationMs" // Operation duration in milliseconds
+
+	// Resource identification
+	FieldName       = "name"       // Resource name
+	FieldNamespace  = "namespace"  // Resource namespace
+	FieldKind       = "kind"       // Resource kind
+	FieldGeneration = "generation" // Resource generation
+
+	// User context
+	FieldUserName = "user" // User making the request
+
+	// Error tracking
+	FieldError   = "error"   // Error indicator
+	FieldSuccess = "success" // Success indicator
+)
+
+// contextKey for storing values in context
+type contextKey struct{ name string }
+
+var (
+	requestIDKey = contextKey{name: "requestID"}
+	loggerKey    = contextKey{name: "logger"}
+)
+
+// Logger wraps logr.Logger with structured logging methods
+type Logger struct {
+	logr.Logger
+}
+
+// WithValues adds key-value pairs to the logger
+func (l Logger) WithValues(keysAndValues ...interface{}) Logger {
+	return Logger{Logger: l.Logger.WithValues(keysAndValues...)}
+}
+
+// New creates a new Logger
+func New() Logger {
+	return Logger{Logger: log.Log}
+}
+
+// WithContext returns a Logger from context or creates a new one
+func WithContext(ctx context.Context) Logger {
+	if logger, ok := ctx.Value(loggerKey).(Logger); ok {
+		return logger
+	}
+	return New()
+}
+
+// IntoContext stores the Logger in context
+func (l Logger) IntoContext(ctx context.Context) context.Context {
+	return context.WithValue(ctx, loggerKey, l)
+}
+
+// WithRequestID stores request ID in context
+func WithRequestID(ctx context.Context, requestID string) context.Context {
+	if requestID == "" {
+		return ctx
+	}
+	return context.WithValue(ctx, requestIDKey, requestID)
+}
+
+// RequestIDFrom retrieves request ID from context
+func RequestIDFrom(ctx context.Context) (string, bool) {
+	id, ok := ctx.Value(requestIDKey).(string)
+	return id, ok && id != ""
+}
+
+// NewHandlerLogger creates a logger for webhook handlers with full request context
+func NewHandlerLogger(ctx context.Context, req admission.Request, handlerName string) Logger {
+	logger := New()
+
+	// Use admission UID as request ID for correlation
+	requestID := string(req.UID)
+	if rid, ok := RequestIDFrom(ctx); ok && rid != "" {
+		requestID = rid
+	}
+
+	// Build structured log with essential fields for observability
+	logger = logger.WithValues(
+		FieldRequestID, requestID,
+		FieldHandler, handlerName,
+		FieldOperation, req.Operation,
+		FieldKind, req.Kind.Kind,
+		FieldName, req.Name,
+		FieldNamespace, req.Namespace,
+		FieldUserName, req.UserInfo.Username,
+	)
+
+	return logger
+}
+
+// Helper methods that return the logger with values added
+// These don't log directly, so the actual logging call site is preserved
+
+// WithStep adds a step field to the logger
+func (l Logger) WithStep(step string) Logger {
+	return l.WithValues(FieldStep, step)
+}
+
+// WithSuccess adds success and duration fields to the logger
+func (l Logger) WithSuccess(success bool, startTime ...time.Time) Logger {
+	logger := l.WithValues(FieldSuccess, success)
+	if len(startTime) > 0 {
+		duration := time.Since(startTime[0])
+		logger = logger.WithValues(FieldDuration, duration.Milliseconds())
+	}
+	return logger
+}
+
+// WithError adds error context to the logger
+func (l Logger) WithError(err error) Logger {
+	return l.WithValues(FieldError, err.Error(), FieldSuccess, false)
+}
+
+// V returns a logger with verbosity level (0=info, 1=debug, 2=trace)
+func (l Logger) V(level int) Logger {
+	return Logger{Logger: l.Logger.V(level)}
+}
+
+// Debug logs debug message (verbosity 1)
+func (l Logger) Debug(msg string, keysAndValues ...interface{}) {
+	l.Logger.V(1).Info(msg, keysAndValues...)
+}
+
+// Trace logs trace message (verbosity 2)
+func (l Logger) Trace(msg string, keysAndValues ...interface{}) {
+	l.Logger.V(2).Info(msg, keysAndValues...)
+}
+
+// Info logs info message
+func (l Logger) Info(msg string, keysAndValues ...interface{}) {
+	l.Logger.Info(msg, keysAndValues...)
+}
+
+// Error logs error message
+func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) {
+	l.Logger.Error(err, msg, keysAndValues...)
+}
diff --git a/pkg/webhook/core.oam.dev/v1beta1/application/validating_handler.go b/pkg/webhook/core.oam.dev/v1beta1/application/validating_handler.go
index cdc27857..b02f65d7 100644
--- a/pkg/webhook/core.oam.dev/v1beta1/application/validating_handler.go
+++ b/pkg/webhook/core.oam.dev/v1beta1/application/validating_handler.go
@@ -21,6 +21,7 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
+	"time"
 
 	admissionv1 "k8s.io/api/admission/v1"
 	"k8s.io/apimachinery/pkg/util/validation/field"
@@ -31,6 +32,7 @@ import (
 
 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
 	controller "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
+	"github.com/oam-dev/kubevela/pkg/logging"
 	"github.com/oam-dev/kubevela/pkg/oam/util"
 )
 
@@ -62,35 +64,78 @@ func mergeErrors(errs field.ErrorList) error {
 
 // Handle validate Application Spec here
 func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
+	startTime := time.Now()
+	ctx = logging.WithRequestID(ctx, string(req.UID))
+	logger := logging.NewHandlerLogger(ctx, req, "ApplicationValidator")
+
+	logger.WithStep("start").Info("Starting admission validation for Application resource", "operation", req.Operation, "applicationName", req.Name, "namespace", req.Namespace)
+
+	// Decode the application
 	app := &v1beta1.Application{}
 	if err := h.Decoder.Decode(req, app); err != nil {
-		return admission.Errored(http.StatusBadRequest, err)
+		logger.WithStep("decode").WithError(err).Error(err, "Unable to decode admission request payload into Application object - malformed request")
+		return admission.Errored(http.StatusBadRequest, fmt.Errorf("failed to decode: %w (requestUID=%s)", err, req.UID))
 	}
+
 	if req.Namespace != "" {
 		app.Namespace = req.Namespace
 	}
 
+	logger = logger.WithValues(logging.FieldGeneration, app.Generation)
+
+	workflowSteps := 0
+	if app.Spec.Workflow != nil {
+		workflowSteps = len(app.Spec.Workflow.Steps)
+	}
+
+	logger.WithStep("decode").Info("Successfully decoded Application from admission request",
+		"applicationName", app.Name,
+		"namespace", app.Namespace,
+		"componentCount", len(app.Spec.Components),
+		"policyCount", len(app.Spec.Policies),
+		"workflowSteps", workflowSteps)
+
 	ctx = util.SetNamespaceInCtx(ctx, app.Namespace)
+
 	switch req.Operation {
 	case admissionv1.Create:
+		logger.WithStep("validate-create").Info("Validating Application creation - checking components, policies, and workflow configuration")
 		if allErrs := h.ValidateCreate(ctx, app, req); len(allErrs) > 0 {
-			// http.StatusUnprocessableEntity will NOT report any error descriptions
-			// to the client, use generic http.StatusBadRequest instead.
-			return admission.Errored(http.StatusBadRequest, mergeErrors(allErrs))
+			mergedErr := mergeErrors(allErrs)
+			logger.WithStep("validate-create").WithError(mergedErr).Error(mergedErr, "Application creation validation failed - contains invalid components, policies, or workflow steps", "errorCount", len(allErrs), "applicationName", app.Name)
+			return admission.Errored(http.StatusBadRequest, fmt.Errorf("%w (requestUID=%s)", mergedErr, req.UID))
 		}
+		logger.WithStep("validate-create").WithSuccess(true).Info("Application creation validation completed successfully - all components, policies, and workflows are valid", "applicationName", app.Name)
+
 	case admissionv1.Update:
+		logger.WithStep("validate-update").Info("Validating Application update - comparing new configuration with existing state")
 		oldApp := &v1beta1.Application{}
 		if err := h.Decoder.DecodeRaw(req.AdmissionRequest.OldObject, oldApp); err != nil {
-			return admission.Errored(http.StatusBadRequest, simplifyError(err))
+			logger.WithStep("decode-old").WithError(err).Error(err, "Unable to decode previous Application state from admission request - cannot validate update")
+			return admission.Errored(http.StatusBadRequest, fmt.Errorf("%w (requestUID=%s)", simplifyError(err), req.UID))
 		}
+
+		logger = logger.WithValues("oldGeneration", oldApp.Generation)
+
 		if app.ObjectMeta.DeletionTimestamp.IsZero() {
 			if allErrs := h.ValidateUpdate(ctx, app, oldApp, req); len(allErrs) > 0 {
-				return admission.Errored(http.StatusBadRequest, mergeErrors(allErrs))
+				mergedErr := mergeErrors(allErrs)
+				logger.WithStep("validate-update").WithError(mergedErr).Error(mergedErr, "Application update validation failed - new configuration contains invalid changes", "errorCount", len(allErrs), "applicationName", app.Name, "oldGeneration", oldApp.Generation, "newGeneration", app.Generation)
+				return admission.Errored(http.StatusBadRequest, fmt.Errorf("%w (requestUID=%s)", mergedErr, req.UID))
 			}
+			logger.WithStep("validate-update").WithSuccess(true).Info("Application update validation completed successfully - configuration changes are valid", "applicationName", app.Name, "generationChange", fmt.Sprintf("%d->%d", oldApp.Generation, app.Generation))
+		} else {
+			logger.WithStep("skip-validation").Info("Skipping Application validation - resource is being deleted and validation is not required", "reason", "deletion-in-progress", "deletionTimestamp", app.DeletionTimestamp)
 		}
+
+	case admissionv1.Delete:
+		logger.WithStep("skip-validation").Info("Skipping Application validation - DELETE operations do not require content validation", "reason", "delete-operation-no-validation-needed")
+
 	default:
-		// Do nothing for DELETE and CONNECT
+		logger.WithStep("skip-validation").Info("Skipping Application validation - operation type is not supported by validator", "operation", req.Operation, "reason", "only CREATE, UPDATE, and DELETE operations are handled")
 	}
+
+	logger.WithStep("complete").WithSuccess(true, startTime).Info("Application admission validation completed successfully - resource will be admitted", "applicationName", req.Name, "operation", req.Operation, "namespace", req.Namespace)
 	return admission.ValidationResponse(true, "")
 }
 
diff --git a/pkg/webhook/core.oam.dev/v1beta1/application/validation.go b/pkg/webhook/core.oam.dev/v1beta1/application/validation.go
index 6d2842c0..a600cba1 100644
--- a/pkg/webhook/core.oam.dev/v1beta1/application/validation.go
+++ b/pkg/webhook/core.oam.dev/v1beta1/application/validation.go
@@ -33,7 +33,6 @@ import (
 
 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
 	"github.com/oam-dev/kubevela/pkg/appfile"
-	"github.com/oam-dev/kubevela/pkg/auth"
 	"github.com/oam-dev/kubevela/pkg/features"
 	"github.com/oam-dev/kubevela/pkg/oam"
 )
@@ -319,8 +318,8 @@ func (h *ValidatingHandler) validateDefinitions(
 
 // ValidateDefinitionPermissions validates that the user has permissions to access all definition types
 func (h *ValidatingHandler) ValidateDefinitionPermissions(ctx context.Context, app *v1beta1.Application, req admission.Request) field.ErrorList {
-	// Requires both authentication and definition validation enabled
-	if !auth.AuthenticationWithUser || !utilfeature.DefaultMutableFeatureGate.Enabled(features.ValidateDefinitionPermissions) {
+	// Check if definition validation is enabled
+	if !utilfeature.DefaultMutableFeatureGate.Enabled(features.ValidateDefinitionPermissions) {
 		return nil
 	}
 
diff --git a/pkg/webhook/core.oam.dev/v1beta1/componentdefinition/component_definition_validating_handler.go b/pkg/webhook/core.oam.dev/v1beta1/componentdefinition/component_definition_validating_handler.go
new file mode 100644
index 00000000..62bc20f6
--- /dev/null
+++ b/pkg/webhook/core.oam.dev/v1beta1/componentdefinition/component_definition_validating_handler.go
@@ -0,0 +1,171 @@
+/*
+ Copyright 2021. The KubeVela Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package componentdefinition
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"time"
+
+	admissionv1 "k8s.io/api/admission/v1"
+	"k8s.io/apimachinery/pkg/api/meta"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
+	"sigs.k8s.io/controller-runtime/pkg/webhook"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
+	"github.com/oam-dev/kubevela/pkg/logging"
+	"github.com/oam-dev/kubevela/pkg/oam"
+	"github.com/oam-dev/kubevela/pkg/oam/util"
+	webhookutils "github.com/oam-dev/kubevela/pkg/webhook/utils"
+)
+
+var componentDefGVR = v1beta1.ComponentDefinitionGVR
+
+// ValidatingHandler handles validation of component definition
+type ValidatingHandler struct {
+	// Decoder decodes object
+	Decoder admission.Decoder
+	Client  client.Client
+}
+
+var _ admission.Handler = &ValidatingHandler{}
+
+// Handle validate ComponentDefinition Spec here
+func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
+	startTime := time.Now()
+	ctx = logging.WithRequestID(ctx, string(req.UID))
+	logger := logging.NewHandlerLogger(ctx, req, "ComponentDefinitionValidator")
+
+	// Using the logger methods directly will show the correct file location
+	logger.WithStep("start").Info("Starting admission validation for ComponentDefinition resource", "operation", req.Operation, "resourceVersion", req.Kind.Version)
+
+	obj := &v1beta1.ComponentDefinition{}
+	if req.Resource.String() != componentDefGVR.String() {
+		err := fmt.Errorf("expect resource to be %s", componentDefGVR)
+		logger.WithStep("resource-check").WithError(err).Error(err, "Admission request targets unexpected resource type - rejecting request",
+			"expected", componentDefGVR.String(),
+			"actual", req.Resource.String(),
+			"operation", req.Operation)
+		return admission.Errored(http.StatusBadRequest, fmt.Errorf("%s (requestUID=%s)", err.Error(), req.UID))
+	}
+
+	if req.Operation == admissionv1.Create || req.Operation == admissionv1.Update {
+		if err := h.Decoder.Decode(req, obj); err != nil {
+			logger.WithStep("decode").WithError(err).Error(err, "Unable to decode admission request payload into ComponentDefinition object - malformed request")
+			return admission.Errored(http.StatusBadRequest, fmt.Errorf("%s (requestUID=%s)", err.Error(), req.UID))
+		}
+
+		// Add definition-specific fields to logger
+		if obj.Spec.Version != "" {
+			logger = logger.WithValues("version", obj.Spec.Version)
+		}
+		logger.WithStep("decode").Info("Successfully decoded ComponentDefinition from admission request",
+			"definitionName", obj.Name,
+			"namespace", obj.Namespace,
+			"workloadType", obj.Spec.Workload.Type,
+			"hasSchematic", obj.Spec.Schematic != nil)
+
+		// Validate workload
+		if err := ValidateWorkload(h.Client.RESTMapper(), obj); err != nil {
+			logger.WithStep("validate-workload").WithError(err).Error(err, "ComponentDefinition workload configuration is invalid - type and definition must be consistent")
+			return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+		}
+		logger.WithStep("validate-workload").Info("ComponentDefinition workload configuration validated successfully", "workloadType", obj.Spec.Workload.Type)
+
+		// Validate CUE template
+		if obj.Spec.Schematic != nil && obj.Spec.Schematic.CUE != nil {
+			logger.WithStep("validate-cue").Info("Validating CUE template syntax and semantics for ComponentDefinition schematic")
+
+			if err := webhookutils.ValidateCuexTemplate(ctx, obj.Spec.Schematic.CUE.Template); err != nil {
+				logger.WithStep("validate-cue").WithError(err).Error(err, "CUE template contains syntax errors or invalid constructs - template compilation failed")
+				return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+			}
+
+			if err := webhookutils.ValidateOutputResourcesExist(obj.Spec.Schematic.CUE.Template, h.Client.RESTMapper(), obj); err != nil {
+				logger.WithStep("validate-output-resources").WithError(err).Error(err, "CUE template references output resources that don't exist in cluster - unknown resource types detected")
+				return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+			}
+			logger.WithStep("validate-cue").WithSuccess(true).Info("CUE template validation completed successfully - template is syntactically correct and all output resources exist")
+		}
+
+		// Validate semantic version
+		if obj.Spec.Version != "" {
+			if err := webhookutils.ValidateSemanticVersion(obj.Spec.Version); err != nil {
+				logger.WithStep("validate-version").WithError(err).Error(err, "ComponentDefinition version does not follow semantic versioning format (x.y.z)", "version", obj.Spec.Version, "expectedFormat", "x.y.z")
+				return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+			}
+			logger.WithStep("validate-version").Info("ComponentDefinition version follows semantic versioning format", "version", obj.Spec.Version)
+		}
+
+		// Validate revision
+		revisionName := obj.GetAnnotations()[oam.AnnotationDefinitionRevisionName]
+		if len(revisionName) != 0 {
+			defRevName := fmt.Sprintf("%s-v%s", obj.Name, revisionName)
+			if err := webhookutils.ValidateDefinitionRevision(ctx, h.Client, obj, client.ObjectKey{Namespace: obj.Namespace, Name: defRevName}); err != nil {
+				logger.WithStep("validate-revision").WithError(err).Error(err, "ComponentDefinition revision conflicts with existing revision or has invalid format", "revisionName", revisionName, "expectedRevisionName", fmt.Sprintf("%s-v%s", obj.Name, revisionName))
+				return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+			}
+			logger.WithStep("validate-revision").Info("ComponentDefinition revision validation completed - no conflicts detected", "revisionName", revisionName)
+		}
+
+		// Check version conflicts
+		if err := webhookutils.ValidateMultipleDefVersionsNotPresent(obj.Spec.Version, revisionName, obj.Kind); err != nil {
+			logger.WithStep("validate-version-conflict").WithError(err).Error(err, "ComponentDefinition has conflicting version specifications - cannot have both spec.version and revision annotation", "specVersion", obj.Spec.Version, "revisionName", revisionName)
+			return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+		}
+
+		// Log successful completion
+		logger.WithStep("complete").WithSuccess(true, startTime).Info("ComponentDefinition admission validation completed successfully - resource is valid and will be admitted", "definitionName", obj.Name, "operation", req.Operation)
+	} else {
+		logger.WithStep("skip-validation").Info("Skipping ComponentDefinition validation - operation does not require validation", "operation", req.Operation, "reason", "only CREATE and UPDATE operations are validated")
+	}
+	return admission.ValidationResponse(true, "")
+}
+
+// RegisterValidatingHandler will register ComponentDefinition validation to webhook
+func RegisterValidatingHandler(mgr manager.Manager) {
+	server := mgr.GetWebhookServer()
+	server.Register("/validating-core-oam-dev-v1beta1-componentdefinitions", &webhook.Admission{Handler: &ValidatingHandler{
+		Client:  mgr.GetClient(),
+		Decoder: admission.NewDecoder(mgr.GetScheme()),
+	}})
+}
+
+// ValidateWorkload validates whether the Workload field is valid
+func ValidateWorkload(mapper meta.RESTMapper, cd *v1beta1.ComponentDefinition) error {
+
+	// If the Type and Definition are all empty, it will be rejected.
+	if cd.Spec.Workload.Type == "" && cd.Spec.Workload.Definition == (common.WorkloadGVK{}) {
+		return fmt.Errorf("neither the type nor the definition of the workload field in the ComponentDefinition %s can be empty", cd.Name)
+	}
+
+	// if Type and Definitiondon‘t point to the same workloaddefinition, it will be rejected.
+	if cd.Spec.Workload.Type != "" && cd.Spec.Workload.Definition != (common.WorkloadGVK{}) {
+		defRef, err := util.ConvertWorkloadGVK2Definition(mapper, cd.Spec.Workload.Definition)
+		if err != nil {
+			return err
+		}
+		if defRef.Name != cd.Spec.Workload.Type {
+			return fmt.Errorf("the type and the definition of the workload field in ComponentDefinition %s should represent the same workload", cd.Name)
+		}
+	}
+	return nil
+}
diff --git a/pkg/webhook/core.oam.dev/v1beta1/componentdefinition/validating_handler.go b/pkg/webhook/core.oam.dev/v1beta1/componentdefinition/validating_handler.go
deleted file mode 100644
index 8622e21a..00000000
--- a/pkg/webhook/core.oam.dev/v1beta1/componentdefinition/validating_handler.go
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- Copyright 2021. The KubeVela Authors.
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-package componentdefinition
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-
-	admissionv1 "k8s.io/api/admission/v1"
-	"k8s.io/apimachinery/pkg/api/meta"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/manager"
-	"sigs.k8s.io/controller-runtime/pkg/webhook"
-	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
-
-	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
-	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
-	"github.com/oam-dev/kubevela/pkg/oam"
-	"github.com/oam-dev/kubevela/pkg/oam/util"
-	webhookutils "github.com/oam-dev/kubevela/pkg/webhook/utils"
-)
-
-var componentDefGVR = v1beta1.ComponentDefinitionGVR
-
-// ValidatingHandler handles validation of component definition
-type ValidatingHandler struct {
-	// Decoder decodes object
-	Decoder admission.Decoder
-	Client  client.Client
-}
-
-var _ admission.Handler = &ValidatingHandler{}
-
-// Handle validate ComponentDefinition Spec here
-func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
-	obj := &v1beta1.ComponentDefinition{}
-	if req.Resource.String() != componentDefGVR.String() {
-		return admission.Errored(http.StatusBadRequest, fmt.Errorf("expect resource to be %s", componentDefGVR))
-	}
-
-	if req.Operation == admissionv1.Create || req.Operation == admissionv1.Update {
-		err := h.Decoder.Decode(req, obj)
-		if err != nil {
-			return admission.Errored(http.StatusBadRequest, err)
-		}
-		err = ValidateWorkload(h.Client.RESTMapper(), obj)
-		if err != nil {
-			return admission.Denied(err.Error())
-		}
-
-		// validate cueTemplate
-		if obj.Spec.Schematic != nil && obj.Spec.Schematic.CUE != nil {
-			err = webhookutils.ValidateCuexTemplate(ctx, obj.Spec.Schematic.CUE.Template)
-			if err != nil {
-				return admission.Denied(err.Error())
-			}
-		}
-
-		if obj.Spec.Version != "" {
-			err = webhookutils.ValidateSemanticVersion(obj.Spec.Version)
-			if err != nil {
-				return admission.Denied(err.Error())
-			}
-		}
-
-		revisionName := obj.GetAnnotations()[oam.AnnotationDefinitionRevisionName]
-		if len(revisionName) != 0 {
-			defRevName := fmt.Sprintf("%s-v%s", obj.Name, revisionName)
-			err = webhookutils.ValidateDefinitionRevision(ctx, h.Client, obj, client.ObjectKey{Namespace: obj.Namespace, Name: defRevName})
-			if err != nil {
-				return admission.Denied(err.Error())
-			}
-		}
-
-		version := obj.Spec.Version
-		err = webhookutils.ValidateMultipleDefVersionsNotPresent(version, revisionName, obj.Kind)
-		if err != nil {
-			return admission.Denied(err.Error())
-		}
-	}
-	return admission.ValidationResponse(true, "")
-}
-
-// RegisterValidatingHandler will register ComponentDefinition validation to webhook
-func RegisterValidatingHandler(mgr manager.Manager) {
-	server := mgr.GetWebhookServer()
-	server.Register("/validating-core-oam-dev-v1beta1-componentdefinitions", &webhook.Admission{Handler: &ValidatingHandler{
-		Client:  mgr.GetClient(),
-		Decoder: admission.NewDecoder(mgr.GetScheme()),
-	}})
-}
-
-// ValidateWorkload validates whether the Workload field is valid
-func ValidateWorkload(mapper meta.RESTMapper, cd *v1beta1.ComponentDefinition) error {
-
-	// If the Type and Definition are all empty, it will be rejected.
-	if cd.Spec.Workload.Type == "" && cd.Spec.Workload.Definition == (common.WorkloadGVK{}) {
-		return fmt.Errorf("neither the type nor the definition of the workload field in the ComponentDefinition %s can be empty", cd.Name)
-	}
-
-	// if Type and Definitiondon‘t point to the same workloaddefinition, it will be rejected.
-	if cd.Spec.Workload.Type != "" && cd.Spec.Workload.Definition != (common.WorkloadGVK{}) {
-		defRef, err := util.ConvertWorkloadGVK2Definition(mapper, cd.Spec.Workload.Definition)
-		if err != nil {
-			return err
-		}
-		if defRef.Name != cd.Spec.Workload.Type {
-			return fmt.Errorf("the type and the definition of the workload field in ComponentDefinition %s should represent the same workload", cd.Name)
-		}
-	}
-	return nil
-}
diff --git a/pkg/webhook/core.oam.dev/v1beta1/policydefinition/policy_definition_validating_handler.go b/pkg/webhook/core.oam.dev/v1beta1/policydefinition/policy_definition_validating_handler.go
new file mode 100644
index 00000000..5246e5c2
--- /dev/null
+++ b/pkg/webhook/core.oam.dev/v1beta1/policydefinition/policy_definition_validating_handler.go
@@ -0,0 +1,129 @@
+/*
+ Copyright 2021. The KubeVela Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package policydefinition
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"time"
+
+	admissionv1 "k8s.io/api/admission/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
+	"sigs.k8s.io/controller-runtime/pkg/webhook"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
+	"github.com/oam-dev/kubevela/pkg/logging"
+	"github.com/oam-dev/kubevela/pkg/oam"
+	webhookutils "github.com/oam-dev/kubevela/pkg/webhook/utils"
+)
+
+var policyDefGVR = v1beta1.PolicyDefinitionGVR
+
+// ValidatingHandler handles validation of policy definition
+type ValidatingHandler struct {
+	// Decoder decodes object
+	Decoder admission.Decoder
+	Client  client.Client
+}
+
+var _ admission.Handler = &ValidatingHandler{}
+
+// Handle validate component definition
+func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
+	startTime := time.Now()
+	ctx = logging.WithRequestID(ctx, string(req.UID))
+	logger := logging.NewHandlerLogger(ctx, req, "PolicyDefinitionValidator")
+
+	logger.WithStep("start").Info("Starting admission validation for PolicyDefinition resource", "operation", req.Operation, "resourceVersion", req.Kind.Version)
+
+	obj := &v1beta1.PolicyDefinition{}
+	if req.Resource.String() != policyDefGVR.String() {
+		err := fmt.Errorf("expect resource to be %s", policyDefGVR)
+		logger.WithStep("resource-check").WithError(err).Error(err, "Admission request targets unexpected resource type - rejecting request",
+			"expected", policyDefGVR.String(),
+			"actual", req.Resource.String(),
+			"operation", req.Operation)
+		return admission.Errored(http.StatusBadRequest, fmt.Errorf("%s (requestUID=%s)", err.Error(), req.UID))
+	}
+
+	if req.Operation == admissionv1.Create || req.Operation == admissionv1.Update {
+		if err := h.Decoder.Decode(req, obj); err != nil {
+			logger.WithStep("decode").WithError(err).Error(err, "Unable to decode admission request payload into PolicyDefinition object - malformed request")
+			return admission.Errored(http.StatusBadRequest, fmt.Errorf("%s (requestUID=%s)", err.Error(), req.UID))
+		}
+		if obj.Spec.Version != "" {
+			logger = logger.WithValues("version", obj.Spec.Version)
+		}
+		logger.WithStep("decode").Info("Successfully decoded PolicyDefinition from admission request",
+			"definitionName", obj.Name,
+			"namespace", obj.Namespace,
+			"hasSchematic", obj.Spec.Schematic != nil,
+			"version", obj.Spec.Version)
+
+		if obj.Spec.Schematic != nil && obj.Spec.Schematic.CUE != nil {
+			logger.WithStep("validate-cue").Info("Validating CUE template syntax and semantics for PolicyDefinition schematic")
+			if err := webhookutils.ValidateCueTemplate(obj.Spec.Schematic.CUE.Template); err != nil {
+				logger.WithStep("validate-cue").WithError(err).Error(err, "CUE template contains syntax errors or invalid constructs - template compilation failed")
+				return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+			}
+			if err := webhookutils.ValidateOutputResourcesExist(obj.Spec.Schematic.CUE.Template, h.Client.RESTMapper(), obj); err != nil {
+				logger.WithStep("validate-output-resources").WithError(err).Error(err, "CUE template references output resources that don't exist in cluster - unknown resource types detected")
+				return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+			}
+			logger.WithStep("validate-cue").WithSuccess(true).Info("CUE template validation completed successfully - template is syntactically correct and all output resources exist")
+		}
+
+		if obj.Spec.Version != "" {
+			if err := webhookutils.ValidateSemanticVersion(obj.Spec.Version); err != nil {
+				logger.WithStep("validate-version").WithError(err).Error(err, "PolicyDefinition version does not follow semantic versioning format (x.y.z)", "version", obj.Spec.Version, "expectedFormat", "x.y.z")
+				return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+			}
+			logger.WithStep("validate-version").Info("PolicyDefinition version follows semantic versioning format", "version", obj.Spec.Version)
+		}
+
+		revisionName := obj.GetAnnotations()[oam.AnnotationDefinitionRevisionName]
+		if len(revisionName) != 0 {
+			defRevName := fmt.Sprintf("%s-v%s", obj.Name, revisionName)
+			if err := webhookutils.ValidateDefinitionRevision(ctx, h.Client, obj, client.ObjectKey{Namespace: obj.Namespace, Name: defRevName}); err != nil {
+				logger.WithStep("validate-revision").WithError(err).Error(err, "PolicyDefinition revision conflicts with existing revision or has invalid format", "revisionName", revisionName, "expectedRevisionName", fmt.Sprintf("%s-v%s", obj.Name, revisionName))
+				return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+			}
+			logger.WithStep("validate-revision").Info("PolicyDefinition revision validation completed - no conflicts detected", "revisionName", revisionName)
+		}
+
+		if err := webhookutils.ValidateMultipleDefVersionsNotPresent(obj.Spec.Version, revisionName, obj.Kind); err != nil {
+			logger.WithStep("validate-version-conflict").WithError(err).Error(err, "PolicyDefinition has conflicting version specifications - cannot have both spec.version and revision annotation", "specVersion", obj.Spec.Version, "revisionName", revisionName)
+			return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+		}
+		logger.WithStep("complete").WithSuccess(true, startTime).Info("PolicyDefinition admission validation completed successfully - resource is valid and will be admitted", "definitionName", obj.Name, "operation", req.Operation)
+	} else {
+		logger.WithStep("skip-validation").Info("Skipping PolicyDefinition validation - operation does not require validation", "operation", req.Operation, "reason", "only CREATE and UPDATE operations are validated")
+	}
+	return admission.ValidationResponse(true, "")
+}
+
+// RegisterValidatingHandler will register ComponentDefinition validation to webhook
+func RegisterValidatingHandler(mgr manager.Manager) {
+	server := mgr.GetWebhookServer()
+	server.Register("/validating-core-oam-dev-v1beta1-policydefinitions", &webhook.Admission{Handler: &ValidatingHandler{
+		Client:  mgr.GetClient(),
+		Decoder: admission.NewDecoder(mgr.GetScheme()),
+	}})
+}
diff --git a/pkg/webhook/core.oam.dev/v1beta1/policydefinition/validating_handler.go b/pkg/webhook/core.oam.dev/v1beta1/policydefinition/validating_handler.go
deleted file mode 100644
index 464c7e85..00000000
--- a/pkg/webhook/core.oam.dev/v1beta1/policydefinition/validating_handler.go
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- Copyright 2021. The KubeVela Authors.
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-package policydefinition
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-
-	admissionv1 "k8s.io/api/admission/v1"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/manager"
-	"sigs.k8s.io/controller-runtime/pkg/webhook"
-	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
-
-	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
-	"github.com/oam-dev/kubevela/pkg/oam"
-	webhookutils "github.com/oam-dev/kubevela/pkg/webhook/utils"
-)
-
-var policyDefGVR = v1beta1.PolicyDefinitionGVR
-
-// ValidatingHandler handles validation of policy definition
-type ValidatingHandler struct {
-	// Decoder decodes object
-	Decoder admission.Decoder
-	Client  client.Client
-}
-
-var _ admission.Handler = &ValidatingHandler{}
-
-// Handle validate component definition
-func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
-	obj := &v1beta1.PolicyDefinition{}
-	if req.Resource.String() != policyDefGVR.String() {
-		return admission.Errored(http.StatusBadRequest, fmt.Errorf("expect resource to be %s", policyDefGVR))
-	}
-
-	if req.Operation == admissionv1.Create || req.Operation == admissionv1.Update {
-		err := h.Decoder.Decode(req, obj)
-		if err != nil {
-			return admission.Errored(http.StatusBadRequest, err)
-		}
-
-		// validate cueTemplate
-		if obj.Spec.Schematic != nil && obj.Spec.Schematic.CUE != nil {
-			err = webhookutils.ValidateCueTemplate(obj.Spec.Schematic.CUE.Template)
-			if err != nil {
-				return admission.Denied(err.Error())
-			}
-		}
-
-		if obj.Spec.Version != "" {
-			err = webhookutils.ValidateSemanticVersion(obj.Spec.Version)
-			if err != nil {
-				return admission.Denied(err.Error())
-			}
-		}
-
-		revisionName := obj.GetAnnotations()[oam.AnnotationDefinitionRevisionName]
-		if len(revisionName) != 0 {
-			defRevName := fmt.Sprintf("%s-v%s", obj.Name, revisionName)
-			err = webhookutils.ValidateDefinitionRevision(ctx, h.Client, obj, client.ObjectKey{Namespace: obj.Namespace, Name: defRevName})
-			if err != nil {
-				return admission.Denied(err.Error())
-			}
-		}
-
-		version := obj.Spec.Version
-		err = webhookutils.ValidateMultipleDefVersionsNotPresent(version, revisionName, obj.Kind)
-		if err != nil {
-			return admission.Denied(err.Error())
-		}
-	}
-	return admission.ValidationResponse(true, "")
-}
-
-// RegisterValidatingHandler will register ComponentDefinition validation to webhook
-func RegisterValidatingHandler(mgr manager.Manager) {
-	server := mgr.GetWebhookServer()
-	server.Register("/validating-core-oam-dev-v1beta1-policydefinitions", &webhook.Admission{Handler: &ValidatingHandler{
-		Client:  mgr.GetClient(),
-		Decoder: admission.NewDecoder(mgr.GetScheme()),
-	}})
-}
diff --git a/pkg/webhook/core.oam.dev/v1beta1/traitdefinition/trait_definition_validating_handler.go b/pkg/webhook/core.oam.dev/v1beta1/traitdefinition/trait_definition_validating_handler.go
new file mode 100644
index 00000000..105cda25
--- /dev/null
+++ b/pkg/webhook/core.oam.dev/v1beta1/traitdefinition/trait_definition_validating_handler.go
@@ -0,0 +1,188 @@
+/*
+Copyright 2021 The KubeVela Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package traitdefinition
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/pkg/errors"
+	admissionv1 "k8s.io/api/admission/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
+	"sigs.k8s.io/controller-runtime/pkg/webhook"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
+	"github.com/oam-dev/kubevela/pkg/appfile"
+	controller "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
+	"github.com/oam-dev/kubevela/pkg/logging"
+	"github.com/oam-dev/kubevela/pkg/oam"
+	webhookutils "github.com/oam-dev/kubevela/pkg/webhook/utils"
+)
+
+const (
+	errValidateDefRef = "error occurs when validating definition reference"
+
+	failInfoDefRefOmitted = "if definition reference is omitted, patch or output with GVK is required"
+)
+
+var traitDefGVR = v1beta1.TraitDefinitionGVR
+
+// ValidatingHandler handles validation of trait definition
+type ValidatingHandler struct {
+	Client client.Client
+
+	// Decoder decodes object
+	Decoder admission.Decoder
+	// Validators validate objects
+	Validators []TraitDefValidator
+}
+
+// TraitDefValidator validate trait definition
+type TraitDefValidator interface {
+	Validate(context.Context, v1beta1.TraitDefinition) error
+}
+
+// TraitDefValidatorFn implements TraitDefValidator
+type TraitDefValidatorFn func(context.Context, v1beta1.TraitDefinition) error
+
+// Validate implements TraitDefValidator method
+func (fn TraitDefValidatorFn) Validate(ctx context.Context, td v1beta1.TraitDefinition) error {
+	return fn(ctx, td)
+}
+
+var _ admission.Handler = &ValidatingHandler{}
+
+// Handle validate trait definition
+func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
+	startTime := time.Now()
+	ctx = logging.WithRequestID(ctx, string(req.UID))
+	logger := logging.NewHandlerLogger(ctx, req, "TraitDefinitionValidator")
+
+	logger.WithStep("start").Info("Starting admission validation for TraitDefinition resource", "operation", req.Operation, "resourceVersion", req.Kind.Version)
+
+	obj := &v1beta1.TraitDefinition{}
+	if req.Resource.String() != traitDefGVR.String() {
+		err := fmt.Errorf("expect resource to be %s", traitDefGVR)
+		logger.WithStep("resource-check").WithError(err).Error(err, "Admission request targets unexpected resource type - rejecting request",
+			"expected", traitDefGVR.String(),
+			"actual", req.Resource.String(),
+			"operation", req.Operation)
+		return admission.Errored(http.StatusBadRequest, fmt.Errorf("%s (requestUID=%s)", err.Error(), req.UID))
+	}
+
+	if req.Operation == admissionv1.Create || req.Operation == admissionv1.Update {
+		if err := h.Decoder.Decode(req, obj); err != nil {
+			logger.WithStep("decode").WithError(err).Error(err, "Unable to decode admission request payload into TraitDefinition object - malformed request")
+			return admission.Errored(http.StatusBadRequest, fmt.Errorf("%s (requestUID=%s)", err.Error(), req.UID))
+		}
+		if obj.Spec.Version != "" {
+			logger = logger.WithValues("version", obj.Spec.Version)
+		}
+		logger.WithStep("decode").Info("Successfully decoded TraitDefinition from admission request",
+			"definitionName", obj.Name,
+			"namespace", obj.Namespace,
+			"hasReference", len(obj.Spec.Reference.Name) > 0,
+			"hasSchematic", obj.Spec.Schematic != nil)
+
+		for i, validator := range h.Validators {
+			if err := validator.Validate(ctx, *obj); err != nil {
+				logger.WithStep(fmt.Sprintf("validator-%d", i)).WithError(err).Error(err, "TraitDefinition custom validator failed - definition does not meet validation requirements", "validatorIndex", i)
+				return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+			}
+		}
+
+		// validate cueTemplate
+		if obj.Spec.Schematic != nil && obj.Spec.Schematic.CUE != nil {
+			logger.WithStep("validate-cue").Info("Validating CUE template syntax and semantics for TraitDefinition schematic")
+			if err := webhookutils.ValidateCuexTemplate(ctx, obj.Spec.Schematic.CUE.Template); err != nil {
+				logger.WithStep("validate-cue").WithError(err).Error(err, "CUE template contains syntax errors or invalid constructs - template compilation failed")
+				return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+			}
+			if err := webhookutils.ValidateOutputResourcesExist(obj.Spec.Schematic.CUE.Template, h.Client.RESTMapper(), obj); err != nil {
+				logger.WithStep("validate-output-resources").WithError(err).Error(err, "CUE template references output resources that don't exist in cluster - unknown resource types detected")
+				return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+			}
+			logger.WithStep("validate-cue").WithSuccess(true).Info("CUE template validation completed successfully - template is syntactically correct and all output resources exist")
+		}
+
+		if obj.Spec.Version != "" {
+			if err := webhookutils.ValidateSemanticVersion(obj.Spec.Version); err != nil {
+				logger.WithStep("validate-version").WithError(err).Error(err, "TraitDefinition version does not follow semantic versioning format (x.y.z)", "version", obj.Spec.Version, "expectedFormat", "x.y.z")
+				return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+			}
+		}
+
+		revisionName := obj.GetAnnotations()[oam.AnnotationDefinitionRevisionName]
+		if len(revisionName) != 0 {
+			defRevName := fmt.Sprintf("%s-v%s", obj.Name, revisionName)
+			if err := webhookutils.ValidateDefinitionRevision(ctx, h.Client, obj, client.ObjectKey{Namespace: obj.Namespace, Name: defRevName}); err != nil {
+				logger.WithStep("validate-revision").WithError(err).Error(err, "TraitDefinition revision conflicts with existing revision or has invalid format", "revisionName", revisionName, "expectedRevisionName", fmt.Sprintf("%s-v%s", obj.Name, revisionName))
+				return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+			}
+		}
+
+		version := obj.Spec.Version
+		if err := webhookutils.ValidateMultipleDefVersionsNotPresent(version, revisionName, obj.Kind); err != nil {
+			logger.WithStep("validate-version-conflict").WithError(err).Error(err, "TraitDefinition has conflicting version specifications - cannot have both spec.version and revision annotation", "specVersion", version, "revisionName", revisionName)
+			return admission.Denied(fmt.Sprintf("%s (requestUID=%s)", err.Error(), req.UID))
+		}
+		logger.WithStep("complete").WithSuccess(true, startTime).Info("TraitDefinition admission validation completed successfully - resource is valid and will be admitted", "definitionName", obj.Name, "operation", req.Operation)
+	} else {
+		logger.WithStep("skip-validation").Info("Skipping TraitDefinition validation - operation does not require validation", "operation", req.Operation, "reason", "only CREATE and UPDATE operations are validated")
+	}
+	return admission.ValidationResponse(true, "")
+}
+
+// RegisterValidatingHandler will register TraitDefinition validation to webhook
+func RegisterValidatingHandler(mgr manager.Manager, _ controller.Args) {
+	server := mgr.GetWebhookServer()
+	server.Register("/validating-core-oam-dev-v1beta1-traitdefinitions", &webhook.Admission{Handler: &ValidatingHandler{
+		Client:  mgr.GetClient(),
+		Decoder: admission.NewDecoder(mgr.GetScheme()),
+		Validators: []TraitDefValidator{
+			TraitDefValidatorFn(ValidateDefinitionReference),
+			// add more validators here
+		},
+	}})
+}
+
+// ValidateDefinitionReference validates whether the trait definition is valid if
+// its `.spec.reference` field is unset.
+// It's valid if
+// it has at least one output, and all outputs must have GVK
+// or it has no output but has a patch
+// or it has a patch and outputs, and all outputs must have GVK
+// TODO(roywang) currently we only validate whether it contains CUE template.
+// Further validation, e.g., output with GVK, valid patch, etc, remains to be done.
+func ValidateDefinitionReference(_ context.Context, td v1beta1.TraitDefinition) error {
+	if len(td.Spec.Reference.Name) > 0 {
+		return nil
+	}
+	capability, err := appfile.ConvertTemplateJSON2Object(td.Name, td.Spec.Extension, td.Spec.Schematic)
+	if err != nil {
+		return errors.WithMessage(err, errValidateDefRef)
+	}
+	if capability.CueTemplate == "" {
+		return errors.New(failInfoDefRefOmitted)
+
+	}
+	return nil
+}
diff --git a/pkg/webhook/core.oam.dev/v1beta1/traitdefinition/validating_handler.go b/pkg/webhook/core.oam.dev/v1beta1/traitdefinition/validating_handler.go
deleted file mode 100644
index a850ae30..00000000
--- a/pkg/webhook/core.oam.dev/v1beta1/traitdefinition/validating_handler.go
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
-Copyright 2021 The KubeVela Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package traitdefinition
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-
-	"github.com/pkg/errors"
-	admissionv1 "k8s.io/api/admission/v1"
-	"k8s.io/klog/v2"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/manager"
-	"sigs.k8s.io/controller-runtime/pkg/webhook"
-	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
-
-	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
-	"github.com/oam-dev/kubevela/pkg/appfile"
-	controller "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
-	"github.com/oam-dev/kubevela/pkg/oam"
-	webhookutils "github.com/oam-dev/kubevela/pkg/webhook/utils"
-)
-
-const (
-	errValidateDefRef = "error occurs when validating definition reference"
-
-	failInfoDefRefOmitted = "if definition reference is omitted, patch or output with GVK is required"
-)
-
-var traitDefGVR = v1beta1.TraitDefinitionGVR
-
-// ValidatingHandler handles validation of trait definition
-type ValidatingHandler struct {
-	Client client.Client
-
-	// Decoder decodes object
-	Decoder admission.Decoder
-	// Validators validate objects
-	Validators []TraitDefValidator
-}
-
-// TraitDefValidator validate trait definition
-type TraitDefValidator interface {
-	Validate(context.Context, v1beta1.TraitDefinition) error
-}
-
-// TraitDefValidatorFn implements TraitDefValidator
-type TraitDefValidatorFn func(context.Context, v1beta1.TraitDefinition) error
-
-// Validate implements TraitDefValidator method
-func (fn TraitDefValidatorFn) Validate(ctx context.Context, td v1beta1.TraitDefinition) error {
-	return fn(ctx, td)
-}
-
-var _ admission.Handler = &ValidatingHandler{}
-
-// Handle validate trait definition
-func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
-	obj := &v1beta1.TraitDefinition{}
-	if req.Resource.String() != traitDefGVR.String() {
-		return admission.Errored(http.StatusBadRequest, fmt.Errorf("expect resource to be %s", traitDefGVR))
-	}
-
-	if req.Operation == admissionv1.Create || req.Operation == admissionv1.Update {
-		err := h.Decoder.Decode(req, obj)
-		if err != nil {
-			return admission.Errored(http.StatusBadRequest, err)
-		}
-		klog.Info("validating ", " name: ", obj.Name, " operation: ", string(req.Operation))
-		for _, validator := range h.Validators {
-			if err := validator.Validate(ctx, *obj); err != nil {
-				klog.Info("validation failed ", " name: ", obj.Name, " errMsgi: ", err.Error())
-				return admission.Denied(err.Error())
-			}
-		}
-
-		// validate cueTemplate
-		if obj.Spec.Schematic != nil && obj.Spec.Schematic.CUE != nil {
-			err = webhookutils.ValidateCuexTemplate(ctx, obj.Spec.Schematic.CUE.Template)
-			if err != nil {
-				return admission.Denied(err.Error())
-			}
-		}
-
-		if obj.Spec.Version != "" {
-			err = webhookutils.ValidateSemanticVersion(obj.Spec.Version)
-			if err != nil {
-				return admission.Denied(err.Error())
-			}
-		}
-
-		revisionName := obj.GetAnnotations()[oam.AnnotationDefinitionRevisionName]
-		if len(revisionName) != 0 {
-			defRevName := fmt.Sprintf("%s-v%s", obj.Name, revisionName)
-			err = webhookutils.ValidateDefinitionRevision(ctx, h.Client, obj, client.ObjectKey{Namespace: obj.Namespace, Name: defRevName})
-			if err != nil {
-				return admission.Denied(err.Error())
-			}
-		}
-
-		version := obj.Spec.Version
-		err = webhookutils.ValidateMultipleDefVersionsNotPresent(version, revisionName, obj.Kind)
-		if err != nil {
-			return admission.Denied(err.Error())
-		}
-		klog.Info("validation passed ", " name: ", obj.Name, " operation: ", string(req.Operation))
-	}
-	return admission.ValidationResponse(true, "")
-}
-
-// RegisterValidatingHandler will register TraitDefinition validation to webhook
-func RegisterValidatingHandler(mgr manager.Manager, _ controller.Args) {
-	server := mgr.GetWebhookServer()
-	server.Register("/validating-core-oam-dev-v1beta1-traitdefinitions", &webhook.Admission{Handler: &ValidatingHandler{
-		Client:  mgr.GetClient(),
-		Decoder: admission.NewDecoder(mgr.GetScheme()),
-		Validators: []TraitDefValidator{
-			TraitDefValidatorFn(ValidateDefinitionReference),
-			// add more validators here
-		},
-	}})
-}
-
-// ValidateDefinitionReference validates whether the trait definition is valid if
-// its `.spec.reference` field is unset.
-// It's valid if
-// it has at least one output, and all outputs must have GVK
-// or it has no output but has a patch
-// or it has a patch and outputs, and all outputs must have GVK
-// TODO(roywang) currently we only validate whether it contains CUE template.
-// Further validation, e.g., output with GVK, valid patch, etc, remains to be done.
-func ValidateDefinitionReference(_ context.Context, td v1beta1.TraitDefinition) error {
-	if len(td.Spec.Reference.Name) > 0 {
-		return nil
-	}
-	capability, err := appfile.ConvertTemplateJSON2Object(td.Name, td.Spec.Extension, td.Spec.Schematic)
-	if err != nil {
-		return errors.WithMessage(err, errValidateDefRef)
-	}
-	if capability.CueTemplate == "" {
-		return errors.New(failInfoDefRefOmitted)
-
-	}
-	return nil
-}
diff --git a/pkg/webhook/core.oam.dev/v1beta1/workflowstepdefinition/validating_handler.go b/pkg/webhook/core.oam.dev/v1beta1/workflowstepdefinition/validating_handler.go
deleted file mode 100644
index f430d368..00000000
--- a/pkg/webhook/core.oam.dev/v1beta1/workflowstepdefinition/validating_handler.go
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
-Copyright 2024 The KubeVela Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package workflowstepdefinition
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-
-	admissionv1 "k8s.io/api/admission/v1"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/manager"
-	"sigs.k8s.io/controller-runtime/pkg/webhook"
-	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
-
-	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
-	"github.com/oam-dev/kubevela/pkg/oam"
-	webhookutils "github.com/oam-dev/kubevela/pkg/webhook/utils"
-)
-
-var workflowStepDefGVR = v1beta1.WorkflowStepDefinitionGVR
-
-// ValidatingHandler handles validation of workflow step definition
-type ValidatingHandler struct {
-	// Decoder decodes object
-	Decoder admission.Decoder
-	Client  client.Client
-}
-
-// InjectClient injects the client into the ValidatingHandler
-func (h *ValidatingHandler) InjectClient(c client.Client) error {
-	h.Client = c
-	return nil
-}
-
-// InjectDecoder injects the decoder into the ValidatingHandler
-func (h *ValidatingHandler) InjectDecoder(d admission.Decoder) error {
-	h.Decoder = d
-	return nil
-}
-
-// Handle validate WorkflowStepDefinition Spec here
-func (h *ValidatingHandler) Handle(_ context.Context, req admission.Request) admission.Response {
-	obj := &v1beta1.WorkflowStepDefinition{}
-	if req.Resource.String() != workflowStepDefGVR.String() {
-		return admission.Errored(http.StatusBadRequest, fmt.Errorf("expect resource to be %s", workflowStepDefGVR))
-	}
-
-	if req.Operation == admissionv1.Create || req.Operation == admissionv1.Update {
-		err := h.Decoder.Decode(req, obj)
-		if err != nil {
-			return admission.Errored(http.StatusBadRequest, err)
-		}
-
-		if obj.Spec.Version != "" {
-			err = webhookutils.ValidateSemanticVersion(obj.Spec.Version)
-			if err != nil {
-				return admission.Denied(err.Error())
-			}
-		}
-		revisionName := obj.Annotations[oam.AnnotationDefinitionRevisionName]
-		version := obj.Spec.Version
-		err = webhookutils.ValidateMultipleDefVersionsNotPresent(version, revisionName, obj.Kind)
-		if err != nil {
-			return admission.Denied(err.Error())
-		}
-	}
-	return admission.ValidationResponse(true, "")
-}
-
-// RegisterValidatingHandler will register WorkflowStepDefinition validation to webhook
-func RegisterValidatingHandler(mgr manager.Manager) {
-	server := mgr.GetWebhookServer()
-	server.Register("/validating-core-oam-dev-v1beta1-workflowstepdefinitions", &webhook.Admission{Handler: &ValidatingHandler{}})
-}
diff --git a/pkg/webhook/core.oam.dev/v1beta1/workflowstepdefinition/workflowstep_validating_handler.go b/pkg/webhook/core.oam.dev/v1beta1/workflowstepdefinition/workflowstep_validating_handler.go
new file mode 100644
index 00000000..e159af12
--- /dev/null
+++ b/pkg/webhook/core.oam.dev/v1beta1/workflowstepdefinition/workflowstep_validating_handler.go
@@ -0,0 +1,148 @@
+/*
+Copyright 2024 The KubeVela Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package workflowstepdefinition provides admission control validation
+// for WorkflowStepDefinition resources in KubeVela.
+package workflowstepdefinition
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"time"
+
+	admissionv1 "k8s.io/api/admission/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
+	"sigs.k8s.io/controller-runtime/pkg/webhook"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
+	"github.com/oam-dev/kubevela/pkg/logging"
+	"github.com/oam-dev/kubevela/pkg/oam"
+	webhookutils "github.com/oam-dev/kubevela/pkg/webhook/utils"
+)
+
+const (
+	// ValidationWebhookPath defines the HTTP path for the validation webhook
+	ValidationWebhookPath = "/validating-core-oam-dev-v1beta1-workflowstepdefinitions"
+)
+
+var (
+	workflowStepDefGVR = v1beta1.WorkflowStepDefinitionGVR
+)
+
+// ValidatingHandler handles validation of WorkflowStepDefinition resources.
+type ValidatingHandler struct {
+	Decoder admission.Decoder
+	Client  client.Client
+}
+
+// InjectClient injects the Kubernetes client into the handler.
+func (h *ValidatingHandler) InjectClient(c client.Client) error {
+	h.Client = c
+	return nil
+}
+
+// InjectDecoder injects the admission decoder into the handler.
+func (h *ValidatingHandler) InjectDecoder(d admission.Decoder) error {
+	h.Decoder = d
+	return nil
+}
+
+// Handle validates WorkflowStepDefinition resources during admission control.
+func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
+	startTime := time.Now()
+	ctx = logging.WithRequestID(ctx, string(req.UID))
+	logger := logging.NewHandlerLogger(ctx, req, "WorkflowStepDefinitionValidator")
+
+	logger.WithStep("start").Info("Starting admission validation for WorkflowStepDefinition resource", "operation", req.Operation, "resourceVersion", req.Kind.Version)
+
+	// Validate resource type
+	if req.Resource.String() != workflowStepDefGVR.String() {
+		err := fmt.Errorf("expected resource to be %s, got %s", workflowStepDefGVR, req.Resource.String())
+		logger.WithStep("resource-check").WithError(err).Error(err, "Admission request targets unexpected resource type - rejecting request",
+			"expected", workflowStepDefGVR.String(),
+			"actual", req.Resource.String(),
+			"operation", req.Operation)
+		return admission.Errored(http.StatusBadRequest, fmt.Errorf("%s (requestUID=%s)", err.Error(), req.UID))
+	}
+
+	// Only validate create and update operations
+	if req.Operation != admissionv1.Create && req.Operation != admissionv1.Update {
+		logger.WithStep("skip-validation").Info("Skipping WorkflowStepDefinition validation - operation does not require validation", "operation", req.Operation, "reason", "only CREATE and UPDATE operations are validated")
+		return admission.ValidationResponse(true, "Operation does not require validation")
+	}
+
+	// Decode the object
+	obj := &v1beta1.WorkflowStepDefinition{}
+	if err := h.Decoder.Decode(req, obj); err != nil {
+		logger.WithStep("decode").WithError(err).Error(err, "Unable to decode admission request payload into WorkflowStepDefinition object - malformed request")
+		return admission.Errored(http.StatusBadRequest, fmt.Errorf("failed to decode: %s (requestUID=%s)", err.Error(), req.UID))
+	}
+
+	if obj.Spec.Version != "" {
+		logger = logger.WithValues("version", obj.Spec.Version)
+	}
+	logger.WithStep("decode").Info("Successfully decoded WorkflowStepDefinition from admission request",
+		"definitionName", obj.Name,
+		"namespace", obj.Namespace,
+		"hasSchematic", obj.Spec.Schematic != nil,
+		"version", obj.Spec.Version)
+
+	// Validate output resources
+	if obj.Spec.Schematic != nil && obj.Spec.Schematic.CUE != nil {
+		logger.WithStep("validate-output-resources").Info("Validating output resources referenced in WorkflowStepDefinition CUE template")
+		if err := webhookutils.ValidateOutputResourcesExist(obj.Spec.Schematic.CUE.Template, h.Client.RESTMapper(), obj); err != nil {
+			logger.WithStep("validate-output-resources").WithError(err).Error(err, "CUE template references output resources that don't exist in cluster - unknown resource types detected")
+			return admission.Denied(fmt.Sprintf("output resource validation failed: %s (requestUID=%s)", err.Error(), req.UID))
+		}
+		logger.WithStep("validate-output-resources").WithSuccess(true).Info("Output resources validation completed successfully - all referenced resources exist in cluster")
+	}
+
+	// Validate semantic version
+	if obj.Spec.Version != "" {
+		if err := webhookutils.ValidateSemanticVersion(obj.Spec.Version); err != nil {
+			logger.WithStep("validate-version").WithError(err).Error(err, "WorkflowStepDefinition version does not follow semantic versioning format (x.y.z)", "version", obj.Spec.Version, "expectedFormat", "x.y.z")
+			return admission.Denied(fmt.Sprintf("semantic version validation failed: %s (requestUID=%s)", err.Error(), req.UID))
+		}
+		logger.WithStep("validate-version").Info("WorkflowStepDefinition version follows semantic versioning format", "version", obj.Spec.Version)
+	}
+
+	// Validate version conflicts
+	revisionName := obj.Annotations[oam.AnnotationDefinitionRevisionName]
+	if err := webhookutils.ValidateMultipleDefVersionsNotPresent(obj.Spec.Version, revisionName, obj.Kind); err != nil {
+		logger.WithStep("validate-version-conflict").WithError(err).Error(err, "WorkflowStepDefinition has conflicting version specifications - cannot have both spec.version and revision annotation", "specVersion", obj.Spec.Version, "revisionName", revisionName)
+		return admission.Denied(fmt.Sprintf("definition version conflict: %s (requestUID=%s)", err.Error(), req.UID))
+	}
+
+	logger.WithStep("complete").WithSuccess(true, startTime).Info("WorkflowStepDefinition admission validation completed successfully - resource is valid and will be admitted", "definitionName", obj.Name, "operation", req.Operation)
+	return admission.ValidationResponse(true, "Validation passed")
+}
+
+// RegisterValidatingHandler registers the WorkflowStepDefinition validation webhook with the manager.
+func RegisterValidatingHandler(mgr manager.Manager) {
+	logger := logging.New()
+	logger.Info("Registering WorkflowStepDefinition validation webhook", "path", ValidationWebhookPath)
+
+	server := mgr.GetWebhookServer()
+	server.Register(ValidationWebhookPath, &webhook.Admission{
+		Handler: &ValidatingHandler{
+			Client:  mgr.GetClient(),
+			Decoder: admission.NewDecoder(mgr.GetScheme()),
+		},
+	})
+}
diff --git a/pkg/webhook/utils/invalid_resource_check.go b/pkg/webhook/utils/invalid_resource_check.go
new file mode 100644
index 00000000..2f2d336c
--- /dev/null
+++ b/pkg/webhook/utils/invalid_resource_check.go
@@ -0,0 +1,257 @@
+/*
+ Copyright 2021. The KubeVela Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package utils
+
+import (
+	"fmt"
+	"strings"
+
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/parser"
+	"cuelang.org/go/cue/token"
+	"k8s.io/apimachinery/pkg/api/meta"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	utilfeature "k8s.io/apiserver/pkg/util/feature"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/oam-dev/kubevela/pkg/features"
+)
+
+// ExtractResourceInfo extracts apiVersion and kind from CUE template without evaluation
+func ExtractResourceInfo(cueTemplate string) ([]ResourceInfo, error) {
+	file, err := parser.ParseFile("", cueTemplate, parser.ParseComments)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse CUE template: %w", err)
+	}
+
+	var resources []ResourceInfo
+
+	// Walk through the AST to find output and outputs fields
+	ast.Walk(file, func(node ast.Node) bool {
+		if n, ok := node.(*ast.Field); ok {
+			label := extractLabel(n.Label)
+			if label == "output" || label == "outputs" {
+				if label == "output" {
+					// Extract from single output field
+					if resource := extractResourceFromStruct(n.Value); resource != nil {
+						resources = append(resources, *resource)
+					}
+				} else if label == "outputs" {
+					// Extract from outputs field (multiple resources)
+					resources = append(resources, extractResourcesFromOutputs(n.Value)...)
+				}
+			}
+		}
+		return true
+	}, nil)
+
+	return resources, nil
+}
+
+type ResourceInfo struct {
+	APIVersion string
+	Kind       string
+	Name       string // optional, for better error messages
+}
+
+func extractLabel(label ast.Label) string {
+	switch l := label.(type) {
+	case *ast.Ident:
+		return l.Name
+	case *ast.BasicLit:
+		if l.Kind == token.STRING {
+			// Remove quotes
+			return strings.Trim(l.Value, `"`)
+		}
+	}
+	return ""
+}
+
+func extractResourceFromStruct(expr ast.Expr) *ResourceInfo {
+	structLit, ok := expr.(*ast.StructLit)
+	if !ok {
+		return nil
+	}
+
+	resource := &ResourceInfo{}
+
+	for _, elt := range structLit.Elts {
+		field, ok := elt.(*ast.Field)
+		if !ok {
+			continue
+		}
+
+		label := extractLabel(field.Label)
+		value := extractStringValue(field.Value)
+
+		switch label {
+		case "apiVersion":
+			resource.APIVersion = value
+		case "kind":
+			resource.Kind = value
+		case "metadata":
+			// Try to extract name from metadata.name
+			if name := extractNameFromMetadata(field.Value); name != "" {
+				resource.Name = name
+			}
+		}
+	}
+
+	// Only return if we have both apiVersion and kind
+	if resource.APIVersion != "" && resource.Kind != "" {
+		return resource
+	}
+	return nil
+}
+
+func extractResourcesFromOutputs(expr ast.Expr) []ResourceInfo {
+	var resources []ResourceInfo
+
+	structLit, ok := expr.(*ast.StructLit)
+	if !ok {
+		return resources
+	}
+
+	for _, elt := range structLit.Elts {
+		field, ok := elt.(*ast.Field)
+		if !ok {
+			continue
+		}
+
+		if resource := extractResourceFromStruct(field.Value); resource != nil {
+			// Use the field label as the resource name if not found in metadata
+			if resource.Name == "" {
+				resource.Name = extractLabel(field.Label)
+			}
+			resources = append(resources, *resource)
+		}
+	}
+
+	return resources
+}
+
+func extractStringValue(expr ast.Expr) string {
+	if e, ok := expr.(*ast.BasicLit); ok {
+		if e.Kind == token.STRING {
+			return strings.Trim(e.Value, `"`)
+		}
+	}
+	return ""
+}
+
+func extractNameFromMetadata(expr ast.Expr) string {
+	structLit, ok := expr.(*ast.StructLit)
+	if !ok {
+		return ""
+	}
+
+	for _, elt := range structLit.Elts {
+		field, ok := elt.(*ast.Field)
+		if !ok {
+			continue
+		}
+
+		if extractLabel(field.Label) == "name" {
+			return extractStringValue(field.Value)
+		}
+	}
+	return ""
+}
+
+// ValidateOutputResourcesExist validates that resources referenced in output/outputs fields exist on the cluster
+func ValidateOutputResourcesExist(cueTemplate string, mapper meta.RESTMapper, obj client.Object) error {
+	// Check if feature gate is enabled FIRST before doing anything
+	if !utilfeature.DefaultMutableFeatureGate.Enabled(features.ValidateResourcesExist) {
+		return nil // Skip validation if feature is disabled
+	}
+
+	// Skip validation for addon definitions
+	// Addons often bundle CRDs with definitions that reference them
+	if IsAddonDefinition(obj) {
+		return nil
+	}
+
+	// Only extract and validate resources if feature is enabled and not an addon
+	resources, err := ExtractResourceInfo(cueTemplate)
+	if err != nil {
+		return fmt.Errorf("failed to extract resource info: %w", err)
+	}
+
+	for _, resource := range resources {
+		gvk := schema.GroupVersionKind{
+			Version: resource.APIVersion,
+			Kind:    resource.Kind,
+		}
+
+		// Parse the apiVersion to get group and version
+		if strings.Contains(resource.APIVersion, "/") {
+			parts := strings.SplitN(resource.APIVersion, "/", 2)
+			gvk.Group = parts[0]
+			gvk.Version = parts[1]
+		} else {
+			// Core API resources (like v1) don't have a group
+			gvk.Group = ""
+			gvk.Version = resource.APIVersion
+		}
+
+		_, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
+		if err != nil {
+			ref := fmt.Sprintf("%s/%s", resource.APIVersion, resource.Kind)
+			return fmt.Errorf("resource type not found on cluster: %s (%w)", ref, err)
+		}
+	}
+
+	return nil
+}
+
+// IsAddonDefinition checks if the object is part of an addon installation
+// This is a generic solution that works for ALL addons by checking owner references
+func IsAddonDefinition(obj client.Object) bool {
+	if obj == nil {
+		return false
+	}
+
+	// Generic approach: Check if the object has an owner reference to an addon application
+	// All addon definitions get owner references to their addon application (pattern: addon-{name})
+	for _, ownerRef := range obj.GetOwnerReferences() {
+		if ownerRef.Kind == "Application" &&
+			ownerRef.APIVersion == "core.oam.dev/v1beta1" &&
+			strings.HasPrefix(ownerRef.Name, "addon-") {
+			return true
+		}
+	}
+
+	// Fallback checks for edge cases where owner references might not be set yet
+	labels := obj.GetLabels()
+	if labels != nil {
+		// Check if this is managed by an addon application
+		appName, hasApp := labels["app.oam.dev/name"]
+		if hasApp && strings.HasPrefix(appName, "addon-") {
+			return true
+		}
+	}
+
+	// Also check annotations for addon markers
+	annotations := obj.GetAnnotations()
+	if annotations != nil {
+		if addonName, hasAddon := annotations["addons.oam.dev/name"]; hasAddon && addonName != "" {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/references/appfile/addon.go b/references/appfile/addon.go
index 5e88da79..448ba298 100644
--- a/references/appfile/addon.go
+++ b/references/appfile/addon.go
@@ -95,11 +95,7 @@ func ApplyTerraform(app *v1beta1.Application, k8sClient client.Client, ioStream
 				return nil, err
 			}
 
-			outputList := strings.Split(strings.ReplaceAll(string(outputs), " ", ""), "\n")
-			if outputList[len(outputList)-1] == "" {
-				outputList = outputList[:len(outputList)-1]
-			}
-			if err := generateSecretFromTerraformOutput(k8sClient, outputList, name, namespace); err != nil {
+			if err := generateSecretFromTerraformOutput(k8sClient, string(outputs), name, namespace); err != nil {
 				return nil, err
 			}
 		default:
@@ -135,21 +131,32 @@ func callTerraform(tfJSONDir string) ([]byte, error) {
 }
 
 // generateSecretFromTerraformOutput generates secret from Terraform output
-func generateSecretFromTerraformOutput(k8sClient client.Client, outputList []string, name, namespace string) error {
+func generateSecretFromTerraformOutput(k8sClient client.Client, rawOutput string, name, namespace string) error {
 	ctx := context.TODO()
-	err := k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}})
-	if err == nil {
-		return fmt.Errorf("namespace %s doesn't exist", namespace)
+
+	// Check if namespace exists
+	var ns v1.Namespace
+	if err := k8sClient.Get(ctx, client.ObjectKey{Name: namespace}, &ns); err != nil {
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("namespace %s doesn't exist", namespace)
+		}
+		return fmt.Errorf("failed to get namespace %s: %w", namespace, err)
 	}
-	var cmData = make(map[string]string, len(outputList))
+
+	var cmData = make(map[string]string)
+	outputList := strings.Split(rawOutput, "\n")
 	for _, i := range outputList {
-		line := strings.Split(i, "=")
+		if strings.TrimSpace(i) == "" {
+			continue
+		}
+		line := strings.SplitN(i, "=", 2)
 		if len(line) != 2 {
-			return fmt.Errorf("terraform output isn't in the right format")
+			return fmt.Errorf("terraform output isn't in the right format: %q", i)
 		}
 		k := strings.TrimSpace(line[0])
-		v := strings.TrimSpace(line[1])
-		if k != "" && v != "" {
+		// Terraform string outputs are quoted, remove them.
+		v := strings.Trim(strings.TrimSpace(line[1]), "\"")
+		if k != "" {
 			cmData[k] = v
 		}
 	}
diff --git a/references/cli/dryrun.go b/references/cli/dryrun.go
index 169f618d..145022ce 100644
--- a/references/cli/dryrun.go
+++ b/references/cli/dryrun.go
@@ -183,7 +183,8 @@ func DryRunApplication(cmdOption *DryRunCmdOptions, c common.Args, namespace str
 	}
 
 	if app.Namespace != "" && namespace != "" && app.Namespace != namespace {
-		return buff, errors.WithMessage(fmt.Errorf("error: conflicting namespace found in file and flag %s doesn't match with namespace %s ", namespace, app.Namespace), "The namespace must be unique")
+		// Overwrite application namespace with flag-provided namespace (change replicated from upstream PR #6855)
+		app.Namespace = namespace
 	}
 
 	switch {
diff --git a/references/cli/test-data/dry-run/testing-dry-run-override.yaml b/references/cli/test-data/dry-run/testing-dry-run-override.yaml
new file mode 100644
index 00000000..f7676e06
--- /dev/null
+++ b/references/cli/test-data/dry-run/testing-dry-run-override.yaml
@@ -0,0 +1,11 @@
+apiVersion: core.oam.dev/v1beta1
+kind: Application
+metadata:
+  name: testing-app-override
+  namespace: original-ns
+spec:
+  components:
+    - name: testing-dryrun
+      type: webservice
+      properties:
+        image: oamdev/hello-world:v1
diff --git a/vela-templates/definitions/internal/workflowstep/vela-cli.cue b/vela-templates/definitions/internal/workflowstep/vela-cli.cue
index 0af351f6..c43f3bd5 100644
--- a/vela-templates/definitions/internal/workflowstep/vela-cli.cue
+++ b/vela-templates/definitions/internal/workflowstep/vela-cli.cue
@@ -133,8 +133,6 @@ template: {
 	}
 
 	parameter: {
-		// +usage=Specify the name of the addon.
-		addonName: string
 		// +usage=Specify the vela command
 		command: [...string]
 		// +usage=Specify the image



Test patch:
diff --git a/.github/actions/e2e-test/README.md b/.github/actions/e2e-test/README.md
new file mode 100644
index 00000000..ef5ca8b9
--- /dev/null
+++ b/.github/actions/e2e-test/README.md
@@ -0,0 +1,51 @@
+# Kubevela K8s Upgrade E2E Test Action
+
+A comprehensive GitHub composite action for running KubeVela Kubernetes upgrade end-to-end (E2E) tests with complete environment setup, multiple test suites, and failure diagnostics.
+
+
+> **Note**: This action requires the `GO_VERSION` environment variable to be set in your workflow.
+
+## Quick Start
+
+### Basic Usage
+
+```yaml
+name: E2E Tests
+on: [push, pull_request]
+
+jobs:
+  e2e-tests:
+    runs-on: ubuntu-latest
+    env:
+      GO_VERSION: '1.23.8'
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        
+      - name: Run KubeVela E2E Tests
+        uses: ./.github/actions/upgrade-e2e-test
+```
+
+## Test Flow Diagram
+
+```
+┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
+│ Environment     │    │ E2E Environment  │    │ Test Execution  │
+│ Setup           │───▶│ Preparation      │───▶│ (3 Suites)      │
+│                 │    │                  │    │                 │
+│ • Install tools │    │ • Cleanup        │    │ • API tests     │
+│ • Setup Go      │    │ • Core setup     │    │ • Addon tests   │
+│ • Dependencies  │    │ • Helm tests     │    │ • General tests │
+│ • Build project │    │                  │    │                 │
+└─────────────────┘    └──────────────────┘    └─────────────────┘
+                                                        │
+                                                        ▼
+                                                ┌─────────────────┐
+                                                │ Diagnostics     │
+                                                │ (On Failure)    │
+                                                │                 │
+                                                │ • Cluster logs  │
+                                                │ • System events │
+                                                │ • Test artifacts│
+                                                └─────────────────┘
+```
\ No newline at end of file
diff --git a/.github/actions/e2e-test/action.yaml b/.github/actions/e2e-test/action.yaml
new file mode 100644
index 00000000..068d6959
--- /dev/null
+++ b/.github/actions/e2e-test/action.yaml
@@ -0,0 +1,96 @@
+name: 'Kubevela K8s Upgrade e2e Test'
+description: 'Runs Kubevela K8s upgrade e2e tests, uploads coverage, and collects diagnostics on failure.'
+
+inputs:
+  codecov-token:
+    description: 'Codecov token for uploading coverage reports'
+    required: false
+    default: ''
+  codecov-enable:
+    description: 'Enable codecov coverage upload'
+    required: false
+    default: 'false'
+
+runs:
+  using: "composite"
+  steps:
+    # ========================================================================
+    # Environment Setup
+    # ========================================================================
+    - name: Configure environment setup
+      uses: ./.github/actions/env-setup
+
+    - name: Build project
+      shell: bash
+      run: make
+
+    # ========================================================================
+    # E2E Test Environment Preparation
+    # ========================================================================
+    - name: Prepare e2e environment
+      shell: bash
+      run: |
+        echo "Preparing e2e test environment..."
+        make e2e-cleanup
+        make e2e-setup-core
+
+        echo "Running Helm tests..."
+        helm test -n vela-system kubevela --timeout 5m
+
+    # ========================================================================
+    # E2E Test Execution
+    # ========================================================================
+    - name: Run API e2e tests
+      shell: bash
+      run: |
+        echo "Running API e2e tests..."
+        make e2e-api-test
+
+    - name: Run addon e2e tests
+      shell: bash
+      run: |
+        echo "Running addon e2e tests..."
+        make e2e-addon-test
+
+    - name: Run general e2e tests
+      shell: bash
+      run: |
+        echo "Running general e2e tests..."
+        make e2e-test
+
+    - name: Upload coverage report
+      if: ${{ inputs.codecov-enable == 'true' }}
+      uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24
+      with:
+        token: ${{ inputs.codecov-token }}
+        files: ./coverage.txt
+        flags: core-unittests
+        name: codecov-umbrella
+        fail_ci_if_error: false
+
+    # ========================================================================
+    # Failure Diagnostics
+    # ========================================================================
+    - name: Collect failure diagnostics
+      if: failure()
+      shell: bash
+      run: |
+        echo "=== FAILURE DIAGNOSTICS ==="
+        echo "Collecting diagnostic information for debugging..."
+
+        echo "--- Cluster Status ---"
+        kubectl get nodes -o wide || true
+        kubectl get pods -A || true
+
+        echo "--- KubeVela System Logs ---"
+        kubectl logs -n vela-system -l app.kubernetes.io/name=vela-core --tail=100 || true
+
+        echo "--- Recent Events ---"
+        kubectl get events -A --sort-by='.lastTimestamp' --field-selector type!=Normal || true
+
+        echo "--- Helm Release Status ---"
+        helm list -A || true
+        helm status kubevela -n vela-system || true
+
+        echo "--- Test Artifacts ---"
+        find . -name "*.log" -type f -exec echo "=== {} ===" \; -exec cat {} \; || true
\ No newline at end of file
diff --git a/.github/actions/multicluster-test/README.md b/.github/actions/multicluster-test/README.md
new file mode 100644
index 00000000..45c69ef5
--- /dev/null
+++ b/.github/actions/multicluster-test/README.md
@@ -0,0 +1,35 @@
+# Kubevela K8s Upgrade Multicluster E2E Test Action
+
+A comprehensive GitHub Actions composite action for running Kubevela Kubernetes upgrade multicluster end-to-end tests with automated coverage reporting and failure diagnostics.
+
+## Usage
+
+
+```yaml
+name: Kubevela Multicluster E2E Tests
+on:
+  push:
+    branches: [main, develop]
+  pull_request:
+    branches: [main]
+
+jobs:
+  multicluster-e2e:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Run Multicluster E2E Tests
+        uses: ./.github/actions/multicluster-test
+        with:
+          codecov-enable: 'true'
+          codecov-token: ${{ secrets.CODECOV_TOKEN }}
+```
+
+## Inputs
+
+| Input | Description | Required | Default | Type |
+|-------|-------------|----------|---------|------|
+| `codecov-token` | Codecov token for uploading coverage reports | No | `''` | string |
+| `codecov-enable` | Enable codecov coverage upload | No | `'false'` | string |
diff --git a/.github/actions/multicluster-test/action.yaml b/.github/actions/multicluster-test/action.yaml
new file mode 100644
index 00000000..61e5882c
--- /dev/null
+++ b/.github/actions/multicluster-test/action.yaml
@@ -0,0 +1,76 @@
+name: 'Kubevela K8s Upgrade Multicluster E2E Test'
+description: 'Runs Kubevela Kubernetes upgrade multicluster end-to-end tests, uploads coverage, and collects diagnostics on failure.'
+author: 'viskumar_gwre'
+
+inputs:
+  codecov-token:
+    description: 'Codecov token for uploading coverage reports'
+    required: false
+    default: ''
+  codecov-enable:
+    description: 'Enable codecov coverage upload'
+    required: false
+    default: 'false'
+
+runs:
+  using: 'composite'
+  steps:
+    # ========================================================================
+    # Environment Setup
+    # ========================================================================
+    - name: Configure environment setup
+      uses: ./.github/actions/env-setup
+
+    # ========================================================================
+    # E2E Test Execution
+    # ========================================================================
+    - name: Prepare e2e test environment
+      shell: bash
+      run: |
+        # Build CLI tools and prepare test environment
+        echo "Building KubeVela CLI..."
+        make vela-cli
+
+        echo "Cleaning up previous test artifacts..."
+        make e2e-cleanup
+
+        echo "Setting up core authentication for e2e tests..."
+        make e2e-setup-core-auth
+
+    - name: Execute multicluster upgrade e2e tests
+      shell: bash
+      run: |
+        # Add built CLI to PATH and run multicluster tests
+        export PATH=$(pwd)/bin:$PATH
+
+        echo "Running e2e multicluster upgrade tests..."
+        make e2e-multicluster-test
+
+    - name: Upload coverage report
+      if: ${{ inputs.codecov-enable == 'true' }}
+      uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24
+      with:
+        token: ${{ inputs.codecov-token }}
+        files: /tmp/e2e-profile.out,/tmp/e2e_multicluster_test.out
+        flags: e2e-multicluster-test
+        name: codecov-umbrella
+
+    # ========================================================================
+    # Failure Diagnostics
+    # ========================================================================
+    - name: Collect failure diagnostics
+      if: failure()
+      shell: bash
+      run: |
+        echo "=== FAILURE DIAGNOSTICS ==="
+        echo "Collecting diagnostic information for debugging..."
+
+        echo "--- Cluster Status ---"
+        kubectl get nodes -o wide || true
+        kubectl get pods -A || true
+
+        echo "--- KubeVela System Logs ---"
+        kubectl logs -n vela-system -l app.kubernetes.io/name=vela-core --tail=100 || true
+
+        echo "--- Recent Events ---"
+        kubectl get events -A --sort-by='.lastTimestamp' --field-selector type!=Normal || true
\ No newline at end of file
diff --git a/.github/actions/unit-test/README.md b/.github/actions/unit-test/README.md
new file mode 100644
index 00000000..492389bc
--- /dev/null
+++ b/.github/actions/unit-test/README.md
@@ -0,0 +1,34 @@
+# Kubevela K8s Upgrade Unit Test Action
+
+A comprehensive GitHub composite action for running KubeVela Kubernetes upgrade unit tests with coverage reporting and failure diagnostics.
+
+## Inputs
+
+| Input | Description | Required | Default |
+|-------|-------------|----------|---------|
+| `codecov-token` | Codecov token for uploading coverage reports | ❌ | `''` |
+| `codecov-enable` | Enable Codecov coverage upload (`'true'` or `'false'`) | ❌ | `'false'` |
+| `go-version` | Go version to use for testing | ❌ | `'1.23.8'` |
+
+## Quick Start
+
+### Basic Usage
+
+```yaml
+name: Unit Tests with Coverage
+on: [push, pull_request]
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        
+      - name: Run KubeVela Unit Tests
+        uses: viskumar_gwre/kubevela-k8s-upgrade-unit-test-action@v1
+        with:
+          codecov-enable: 'true'
+          codecov-token: ${{ secrets.CODECOV_TOKEN }}
+          go-version: '1.23.8'
+```
\ No newline at end of file
diff --git a/.github/actions/unit-test/action.yaml b/.github/actions/unit-test/action.yaml
new file mode 100644
index 00000000..e5cf2e19
--- /dev/null
+++ b/.github/actions/unit-test/action.yaml
@@ -0,0 +1,67 @@
+name: 'Kubevela K8s Upgrade Unit Test'
+description: 'Runs Kubevela K8s upgrade unit tests, uploads coverage, and collects diagnostics on failure.'
+
+inputs:
+  codecov-token:
+    description: 'Codecov token for uploading coverage reports'
+    required: false
+    default: ''
+  codecov-enable:
+    description: 'Enable codecov coverage upload'
+    required: false
+    default: 'false'
+
+runs:
+  using: "composite"
+  steps:
+    # ========================================================================
+    # Environment Setup
+    # ========================================================================
+    - name: Configure environment setup
+      uses: ./.github/actions/env-setup
+
+    # ========================================================================
+    # Unit Test Execution
+    # ========================================================================
+    - name: Run unit tests
+      shell: bash
+      run: |
+        echo "Running unit tests..."
+        make test
+
+    - name: Upload coverage report
+      if: ${{ inputs.codecov-enable == 'true' }}
+      uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24
+      with:
+        token: ${{ inputs.codecov-token }}
+        files: ./coverage.txt
+        flags: core-unittests
+        name: codecov-umbrella
+        fail_ci_if_error: false
+
+    # ========================================================================
+    # Failure Diagnostics
+    # ========================================================================
+    - name: Collect failure diagnostics
+      if: failure()
+      shell: bash
+      run: |
+        echo "=== FAILURE DIAGNOSTICS ==="
+        echo "Collecting diagnostic information for debugging..."
+
+        echo "--- Go Environment ---"
+        go version || true
+        go env || true
+
+        echo "--- Cluster Status ---"
+        kubectl get nodes -o wide || true
+        kubectl get pods -A || true
+
+        echo "--- KubeVela System Logs ---"
+        kubectl logs -n vela-system -l app.kubernetes.io/name=vela-core --tail=100 || true
+
+        echo "--- Recent Events ---"
+        kubectl get events -A --sort-by='.lastTimestamp' --field-selector type!=Normal || true
+
+        echo "--- Test Artifacts ---"
+        find . -name "*.log" -o -name "*test*.xml" -o -name "coverage.*" | head -20 || true
\ No newline at end of file
diff --git a/e2e/plugin/plugin_test.go b/e2e/plugin/plugin_test.go
index 8efe5871..06c033c3 100644
--- a/e2e/plugin/plugin_test.go
+++ b/e2e/plugin/plugin_test.go
@@ -425,7 +425,7 @@ spec:
         }
         
         outputs: ingress: {
-        	apiVersion: "networking.k8s.io/v1beta1"
+        	apiVersion: "networking.k8s.io/v1"
         	kind:       "Ingress"
         	metadata:
         		name: context.name
@@ -436,9 +436,14 @@ spec:
         				paths: [
         					for k, v in parameter.http {
         						path: k
+        						pathType: "Prefix"
         						backend: {
-        							serviceName: context.name
-        							servicePort: v
+        							service: {
+        								name: context.name
+        								port: {
+        									number: v
+        								}
+        							}
         						}
         					},
         				]
@@ -617,7 +622,7 @@ spec:
 
 ---
 ## From the trait test-ingress 
-apiVersion: networking.k8s.io/v1beta1
+apiVersion: networking.k8s.io/v1
 kind: Ingress
 metadata:
   annotations: {}
@@ -637,9 +642,12 @@ spec:
     http:
       paths:
       - backend:
-          serviceName: express-server
-          servicePort: 80
+          service:
+            name: express-server
+            port:
+              number: 80
         path: /
+        pathType: Prefix
 
 ---
 `
@@ -721,7 +729,7 @@ var livediffResult = `Application (test-vela-app) has been modified(*)
 -     app.oam.dev/component: express-server
   
 * Component (express-server) / Trait (test-ingress/ingress) has been removed(-)
-- apiVersion: networking.k8s.io/v1beta1
+- apiVersion: networking.k8s.io/v1
 - kind: Ingress
 - metadata:
 -   labels:
@@ -739,9 +747,12 @@ var livediffResult = `Application (test-vela-app) has been modified(*)
 -     http:
 -       paths:
 -       - backend:
--           serviceName: express-server
--           servicePort: 80
+-           service:
+-             name: express-server
+-             port:
+-               number: 80
 -         path: /
+-         pathType: Prefix
   
 * Component (new-express-server) has been added(+)
 + apiVersion: apps/v1
@@ -796,7 +807,7 @@ var livediffResult = `Application (test-vela-app) has been modified(*)
 +     app.oam.dev/component: new-express-server
   
 * Component (new-express-server) / Trait (test-ingress/ingress) has been added(+)
-+ apiVersion: networking.k8s.io/v1beta1
++ apiVersion: networking.k8s.io/v1
 + kind: Ingress
 + metadata:
 +   labels:
@@ -814,9 +825,12 @@ var livediffResult = `Application (test-vela-app) has been modified(*)
 +     http:
 +       paths:
 +       - backend:
-+           serviceName: new-express-server
-+           servicePort: 8080
++           service:
++             name: new-express-server
++             port:
++               number: 8080
 +         path: /
++         pathType: Prefix
 `
 
 var testShowComponentDef = `
diff --git a/pkg/addon/error_test.go b/pkg/addon/error_test.go
index 05709d37..d9066211 100644
--- a/pkg/addon/error_test.go
+++ b/pkg/addon/error_test.go
@@ -20,6 +20,8 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/google/go-github/v32/github"
+	"github.com/pkg/errors"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -41,3 +43,13 @@ func TestGetAvailableVersion(t *testing.T) {
 	assert.NotEmpty(t, err)
 	assert.Equal(t, version, "")
 }
+
+func TestWrapErrRateLimit(t *testing.T) {
+	regularErr := errors.New("regular error")
+	wrappedErr := WrapErrRateLimit(regularErr)
+	assert.Equal(t, regularErr, wrappedErr)
+
+	rateLimitErr := &github.RateLimitError{}
+	wrappedErr = WrapErrRateLimit(rateLimitErr)
+	assert.Equal(t, ErrRateLimit, wrappedErr)
+}
diff --git a/pkg/addon/helper_test.go b/pkg/addon/helper_test.go
index 15c6dbcf..0d3dd0de 100644
--- a/pkg/addon/helper_test.go
+++ b/pkg/addon/helper_test.go
@@ -39,7 +39,7 @@ func setupMockServer() *httptest.Server {
 			"index.yaml",
 			"fluxcd-test-version-1.0.0.tgz",
 			"fluxcd-test-version-2.0.0.tgz",
-			"vela-workflow-v0.3.5.tgz",
+			"vela-workflow-v0.6.2.tgz",
 			"foo-v1.0.0.tgz",
 			"bar-v1.0.0.tgz",
 			"bar-v2.0.0.tgz",
diff --git a/pkg/addon/memory_reader_test.go b/pkg/addon/memory_reader_test.go
deleted file mode 100644
index d778b02d..00000000
--- a/pkg/addon/memory_reader_test.go
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
-Copyright 2021 The KubeVela Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package addon
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-	"helm.sh/helm/v3/pkg/chart/loader"
-)
-
-var files = []*loader.BufferedFile{
-	{
-		Name: "metadata.yaml",
-		Data: []byte(`name: test-helm-addon
-version: 1.0.0
-description: This is a addon for test when install addon from helm repo
-icon: https://www.terraform.io/assets/images/logo-text-8c3ba8a6.svg
-url: https://terraform.io/
-
-tags: []
-
-deployTo:
-  control_plane: true
-  runtime_cluster: false
-
-dependencies: []
-
-invisible: false`),
-	},
-	{
-		Name: "/resources/parameter.cue",
-		Data: []byte(`parameter: {
-	// test wrong parameter
-	example: *"default"
-}`),
-	},
-}
-
-func TestMemoryReader(t *testing.T) {
-	m := MemoryReader{
-		Name:  "fluxcd",
-		Files: files,
-	}
-
-	meta, err := m.ListAddonMeta()
-	assert.NoError(t, err)
-	assert.Equal(t, len(meta["fluxcd"].Items), 2)
-
-	metaFile, err := m.ReadFile("metadata.yaml")
-	assert.NoError(t, err)
-	assert.NotEmpty(t, metaFile)
-
-	parameterData, err := m.ReadFile("/resources/parameter.cue")
-	assert.NoError(t, err)
-	assert.NotEmpty(t, parameterData)
-}
diff --git a/pkg/addon/reader_gitee_test.go b/pkg/addon/reader_gitee_test.go
index 2f6f9d02..a242e070 100644
--- a/pkg/addon/reader_gitee_test.go
+++ b/pkg/addon/reader_gitee_test.go
@@ -97,3 +97,155 @@ func TestGiteeReader(t *testing.T) {
 	assert.NoError(t, err)
 
 }
+
+func TestNewGiteeClient(t *testing.T) {
+	defaultURL, _ := url.Parse(DefaultGiteeURL)
+
+	testCases := map[string]struct {
+		httpClient *http.Client
+		baseURL    *url.URL
+		wantClient *http.Client
+		wantURL    *url.URL
+	}{
+		"Nil inputs": {
+			httpClient: nil,
+			baseURL:    nil,
+			wantClient: &http.Client{},
+			wantURL:    defaultURL,
+		},
+		"Custom inputs": {
+			httpClient: &http.Client{Timeout: 10},
+			baseURL:    &url.URL{Host: "my-gitee.com"},
+			wantClient: &http.Client{Timeout: 10},
+			wantURL:    &url.URL{Host: "my-gitee.com"},
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			client := NewGiteeClient(tc.httpClient, tc.baseURL)
+			assert.Equal(t, tc.wantClient.Timeout, client.Client.Timeout)
+			assert.Equal(t, tc.wantURL.Host, client.BaseURL.Host)
+		})
+	}
+}
+
+func TestGiteeReaderRelativePath(t *testing.T) {
+	testCases := map[string]struct {
+		basePath     string
+		itemPath     string
+		expectedPath string
+	}{
+		"No base path": {
+			basePath:     "",
+			itemPath:     "fluxcd/metadata.yaml",
+			expectedPath: "fluxcd/metadata.yaml",
+		},
+		"With base path": {
+			basePath:     "addons",
+			itemPath:     "addons/fluxcd/metadata.yaml",
+			expectedPath: "fluxcd/metadata.yaml",
+		},
+		"With deep base path": {
+			basePath:     "official/addons",
+			itemPath:     "official/addons/fluxcd/template.cue",
+			expectedPath: "fluxcd/template.cue",
+		},
+		"Item at root of base path": {
+			basePath:     "addons",
+			itemPath:     "addons/README.md",
+			expectedPath: "README.md",
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			gith := &giteeHelper{
+				Meta: &utils.Content{GiteeContent: utils.GiteeContent{
+					Path: tc.basePath,
+				}},
+			}
+			r := &giteeReader{h: gith}
+			item := &github.RepositoryContent{Path: &tc.itemPath}
+
+			result := r.RelativePath(item)
+			assert.Equal(t, tc.expectedPath, result)
+		})
+	}
+}
+
+func TestGiteeReader_ListAddonMeta(t *testing.T) {
+	client, mux, teardown := giteeSetup()
+	defer teardown()
+
+	giteePattern := "/repos/o/r/contents/"
+	mux.HandleFunc(giteePattern, func(rw http.ResponseWriter, req *http.Request) {
+		var contents []*github.RepositoryContent
+		queryPath := strings.TrimPrefix(req.URL.Path, giteePattern)
+
+		switch queryPath {
+		case "": // Root directory
+			contents = []*github.RepositoryContent{
+				{Type: String("dir"), Name: String("fluxcd"), Path: String("fluxcd")},
+				{Type: String("dir"), Name: String("velaux"), Path: String("velaux")},
+				{Type: String("file"), Name: String("README.md"), Path: String("README.md")},
+			}
+		case "fluxcd":
+			contents = []*github.RepositoryContent{
+				{Type: String("file"), Name: String("metadata.yaml"), Path: String("fluxcd/metadata.yaml")},
+				{Type: String("dir"), Name: String("resources"), Path: String("fluxcd/resources")},
+			}
+		case "fluxcd/resources":
+			contents = []*github.RepositoryContent{
+				{Type: String("file"), Name: String("parameter.cue"), Path: String("fluxcd/resources/parameter.cue")},
+			}
+		case "velaux":
+			contents = []*github.RepositoryContent{
+				{Type: String("file"), Name: String("metadata.yaml"), Path: String("velaux/metadata.yaml")},
+			}
+		default:
+			rw.WriteHeader(http.StatusNotFound)
+			return
+		}
+		res, _ := json.Marshal(contents)
+		rw.Write(res)
+	})
+
+	gith := &giteeHelper{
+		Client: client,
+		Meta: &utils.Content{GiteeContent: utils.GiteeContent{
+			Owner: "o",
+			Repo:  "r",
+		}},
+	}
+	r := &giteeReader{h: gith}
+
+	meta, err := r.ListAddonMeta()
+	assert.NoError(t, err)
+	assert.NotNil(t, meta)
+	assert.Equal(t, 2, len(meta), "Expected to find 2 addons, root files should be ignored")
+
+	t.Run("fluxcd addon discovery", func(t *testing.T) {
+		addon, ok := meta["fluxcd"]
+		assert.True(t, ok, "fluxcd addon should be discovered")
+		assert.Equal(t, "fluxcd", addon.Name)
+
+		// Should find 2 items recursively: metadata.yaml and resources/parameter.cue
+		assert.Equal(t, 2, len(addon.Items), "fluxcd should contain 2 files")
+
+		foundPaths := make(map[string]bool)
+		for _, item := range addon.Items {
+			foundPaths[item.GetPath()] = true
+		}
+		assert.True(t, foundPaths["fluxcd/metadata.yaml"], "should find fluxcd/metadata.yaml")
+		assert.True(t, foundPaths["fluxcd/resources/parameter.cue"], "should find fluxcd/resources/parameter.cue")
+	})
+
+	t.Run("velaux addon discovery", func(t *testing.T) {
+		addon, ok := meta["velaux"]
+		assert.True(t, ok, "velaux addon should be discovered")
+		assert.Equal(t, "velaux", addon.Name)
+		assert.Equal(t, 1, len(addon.Items), "velaux should contain 1 file")
+		assert.Equal(t, "velaux/metadata.yaml", addon.Items[0].GetPath())
+	})
+}
diff --git a/pkg/addon/reader_github_test.go b/pkg/addon/reader_github_test.go
index 1d971c03..5ef217d4 100644
--- a/pkg/addon/reader_github_test.go
+++ b/pkg/addon/reader_github_test.go
@@ -64,8 +64,10 @@ func setup() (client *github.Client, mux *http.ServeMux, teardown func()) {
 	return client, mux, server.Close
 }
 
-func TestGitHubReader(t *testing.T) {
+func TestGitHubReader_ReadFile(t *testing.T) {
 	client, mux, teardown := setup()
+	defer teardown()
+
 	githubPattern := "/repos/o/r/contents/"
 	mux.HandleFunc(githubPattern, func(rw http.ResponseWriter, req *http.Request) {
 		queryPath := strings.TrimPrefix(req.URL.Path, githubPattern)
@@ -76,6 +78,7 @@ func TestGitHubReader(t *testing.T) {
 			content := &github.RepositoryContent{Type: String("file"), Name: String(path.Base(queryPath)), Size: Int(len(file)), Encoding: String(""), Path: String(queryPath), Content: String(string(file))}
 			res, _ := json.Marshal(content)
 			rw.Write(res)
+			return
 		}
 
 		// otherwise, it could be directory
@@ -91,11 +94,11 @@ func TestGitHubReader(t *testing.T) {
 			}
 			dRes, _ := json.Marshal(contents)
 			rw.Write(dRes)
+			return
 		}
 
-		rw.Write([]byte("invalid github query"))
+		rw.WriteHeader(http.StatusNotFound)
 	})
-	defer teardown()
 
 	gith := &gitHelper{
 		Client: client,
@@ -107,7 +110,95 @@ func TestGitHubReader(t *testing.T) {
 	var r AsyncReader = &gitReader{gith}
 	_, err := r.ReadFile("example/metadata.yaml")
 	assert.NoError(t, err)
+}
+
+func TestGitReader_RelativePath(t *testing.T) {
+	testCases := map[string]struct {
+		basePath     string
+		itemPath     string
+		expectedPath string
+	}{
+		"No base path": {
+			basePath:     "",
+			itemPath:     "fluxcd/metadata.yaml",
+			expectedPath: "fluxcd/metadata.yaml",
+		},
+		"With base path": {
+			basePath:     "addons",
+			itemPath:     "addons/fluxcd/metadata.yaml",
+			expectedPath: "fluxcd/metadata.yaml",
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			gith := &gitHelper{
+				Meta: &utils.Content{GithubContent: utils.GithubContent{
+					Path: tc.basePath,
+				}},
+			}
+			r := &gitReader{h: gith}
+			item := &github.RepositoryContent{Path: &tc.itemPath}
+
+			result := r.RelativePath(item)
+			assert.Equal(t, tc.expectedPath, result)
+		})
+	}
+}
+
+func TestGitReader_ListAddonMeta(t *testing.T) {
+	client, mux, teardown := setup()
+	defer teardown()
+
+	githubPattern := "/repos/o/r/contents/"
+	mux.HandleFunc(githubPattern, func(rw http.ResponseWriter, req *http.Request) {
+		var contents []*github.RepositoryContent
+		queryPath := strings.TrimPrefix(req.URL.Path, githubPattern)
 
+		switch queryPath {
+		case "": // Root directory
+			contents = []*github.RepositoryContent{
+				{Type: String("dir"), Name: String("fluxcd"), Path: String("fluxcd")},
+				{Type: String("file"), Name: String("README.md"), Path: String("README.md")},
+			}
+		case "fluxcd":
+			contents = []*github.RepositoryContent{
+				{Type: String("file"), Name: String("metadata.yaml"), Path: String("fluxcd/metadata.yaml")},
+				{Type: String("dir"), Name: String("resources"), Path: String("fluxcd/resources")},
+				{Type: String("file"), Name: String("template.cue"), Path: String("fluxcd/template.cue")},
+			}
+		case "fluxcd/resources":
+			contents = []*github.RepositoryContent{
+				{Type: String("file"), Name: String("parameter.cue"), Path: String("fluxcd/resources/parameter.cue")},
+			}
+		default:
+			rw.WriteHeader(http.StatusNotFound)
+			return
+		}
+		res, _ := json.Marshal(contents)
+		rw.Write(res)
+	})
+
+	gith := &gitHelper{
+		Client: client,
+		Meta: &utils.Content{GithubContent: utils.GithubContent{
+			Owner: "o",
+			Repo:  "r",
+		}},
+	}
+	r := &gitReader{h: gith}
+
+	meta, err := r.ListAddonMeta()
+	assert.NoError(t, err)
+	assert.NotNil(t, meta)
+	assert.Equal(t, 1, len(meta), "Expected to find 1 addon, root files should be ignored")
+
+	t.Run("fluxcd addon discovery", func(t *testing.T) {
+		addon, ok := meta["fluxcd"]
+		assert.True(t, ok, "fluxcd addon should be discovered")
+		assert.Equal(t, "fluxcd", addon.Name)
+		assert.Equal(t, 3, len(addon.Items), "fluxcd should contain 3 files")
+	})
 }
 
 // Int is a helper routine that allocates a new int value
diff --git a/pkg/addon/reader_gitlab_test.go b/pkg/addon/reader_gitlab_test.go
index e193767a..4c0ace62 100644
--- a/pkg/addon/reader_gitlab_test.go
+++ b/pkg/addon/reader_gitlab_test.go
@@ -21,8 +21,7 @@ import (
 	"encoding/json"
 	"net/http"
 	"net/http/httptest"
-	"path"
-	"strings"
+	"strconv"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -33,81 +32,190 @@ import (
 
 var baseUrl = "/api/v4"
 
-func gitlabSetup() (client *gitlab.Client, mux *http.ServeMux, teardown func()) {
-	// mux is the HTTP request multiplexer used with the test server.
-	mux = http.NewServeMux()
-
+func gitlabSetup(t *testing.T) (*gitlab.Client, *http.ServeMux, func()) {
+	mux := http.NewServeMux()
 	apiHandler := http.NewServeMux()
 	apiHandler.Handle(baseUrl+"/", http.StripPrefix(baseUrl, mux))
-
-	// server is a test HTTP server used to provide mock API responses.
 	server := httptest.NewServer(apiHandler)
 
-	// client is the Gitlab client being tested and is
-	// configured to use test server.
 	client, err := gitlab.NewClient("", gitlab.WithBaseURL(server.URL+baseUrl+"/"))
-	if err != nil {
-		return
-	}
+	assert.NoError(t, err)
 
 	return client, mux, server.Close
 }
 
-func TestGitlabReader(t *testing.T) {
-	client, mux, teardown := gitlabSetup()
-	gitlabPattern := "/projects/9999/repository/files/"
-	mux.HandleFunc(gitlabPattern, func(rw http.ResponseWriter, req *http.Request) {
-		queryPath := strings.TrimPrefix(req.URL.Path, gitlabPattern)
-		localPath := path.Join(testdataPrefix, queryPath)
-		file, err := testdata.ReadFile(localPath)
-		// test if it's a file
-		if err == nil {
-			content := &gitlab.File{
-				FilePath: localPath,
-				FileName: path.Base(queryPath),
-				Size:     *Int(len(file)),
-				Encoding: "base64",
-				Ref:      "master",
-				Content:  base64.StdEncoding.EncodeToString(file),
-			}
-			res, _ := json.Marshal(content)
-			rw.Write(res)
-			return
+func TestGitlabReader_ReadFile(t *testing.T) {
+	client, mux, teardown := gitlabSetup(t)
+	defer teardown()
+
+	// The gitlab client URL-encodes the file path, so we must match the encoded path.
+	mux.HandleFunc("/projects/9999/repository/files/example%2Fmetadata.yaml", func(rw http.ResponseWriter, req *http.Request) {
+		content := &gitlab.File{
+			Content: base64.StdEncoding.EncodeToString([]byte("hello world")),
 		}
+		res, err := json.Marshal(content)
+		assert.NoError(t, err)
+		_, err = rw.Write(res)
+		assert.NoError(t, err)
+	})
+	mux.HandleFunc("/projects/9999/repository/files/example%2Fnot%2Ffound.yaml", func(rw http.ResponseWriter, req *http.Request) {
+		rw.WriteHeader(http.StatusNotFound)
+	})
+
+	gith := &gitlabHelper{
+		Client: client,
+		Meta:   &utils.Content{GitlabContent: utils.GitlabContent{PId: 9999, Path: "example"}},
+	}
+	var r AsyncReader = &gitlabReader{h: gith}
+
+	t.Run("success case", func(t *testing.T) {
+		content, err := r.ReadFile("metadata.yaml")
+		assert.NoError(t, err)
+		assert.Equal(t, "hello world", content)
+	})
+
+	t.Run("not found case", func(t *testing.T) {
+		_, err := r.ReadFile("not/found.yaml")
+		assert.Error(t, err)
+	})
+}
+
+func TestGitlabReader_ListAddonMeta(t *testing.T) {
+	client, mux, teardown := gitlabSetup(t)
+	defer teardown()
+
+	projectID := 9999
+	projectPath := "addons"
+
+	mux.HandleFunc("/projects/"+strconv.Itoa(projectID)+"/repository/tree", func(rw http.ResponseWriter, req *http.Request) {
+		pathParam := req.URL.Query().Get("path")
+		pageParam := req.URL.Query().Get("page")
+		if pageParam == "" {
+			pageParam = "1"
+		}
+
+		var tree []*gitlab.TreeNode
 
-		// otherwise, it could be directory
-		dir, err := testdata.ReadDir(localPath)
-		if err == nil {
-			contents := make([]*gitlab.TreeNode, 0)
-			for _, item := range dir {
-				tp := "file"
-				if item.IsDir() {
-					tp = "dir"
-				}
-				contents = append(contents, &gitlab.TreeNode{
-					ID:   "",
-					Name: item.Name(),
-					Type: tp,
-					Path: localPath + "/" + item.Name(),
-					Mode: "",
-				})
+		switch pathParam {
+		case projectPath:
+			rw.Header().Set("X-Total-Pages", "2")
+			if pageParam == "1" {
+				tree = []*gitlab.TreeNode{{ID: "1", Name: "fluxcd", Type: "tree", Path: "addons/fluxcd"}}
+			} else if pageParam == "2" {
+				tree = []*gitlab.TreeNode{{ID: "2", Name: "velaux", Type: "tree", Path: "addons/velaux"}}
 			}
-			dRes, _ := json.Marshal(contents)
-			rw.Write(dRes)
+		case "addons/fluxcd":
+			tree = []*gitlab.TreeNode{{ID: "3", Name: "metadata.yaml", Type: "blob", Path: "addons/fluxcd/metadata.yaml"}}
+		case "addons/velaux":
+			tree = []*gitlab.TreeNode{{ID: "4", Name: "template.cue", Type: "blob", Path: "addons/velaux/template.cue"}}
+		default:
+			rw.WriteHeader(http.StatusNotFound)
 			return
 		}
-
-		rw.Write([]byte("invalid gitlab query"))
+		res, err := json.Marshal(tree)
+		assert.NoError(t, err)
+		_, err = rw.Write(res)
+		assert.NoError(t, err)
 	})
-	defer teardown()
 
 	gith := &gitlabHelper{
 		Client: client,
 		Meta: &utils.Content{GitlabContent: utils.GitlabContent{
-			PId: 9999,
+			PId:  projectID,
+			Path: projectPath,
 		}},
 	}
-	var r AsyncReader = &gitlabReader{gith}
-	_, err := r.ReadFile("example/metadata.yaml")
+	r := &gitlabReader{h: gith}
+
+	meta, err := r.ListAddonMeta()
 	assert.NoError(t, err)
+	assert.NotNil(t, meta)
+	assert.Equal(t, 2, len(meta))
+
+	expectedAddons := map[string]struct {
+		itemCount int
+		itemPath  string
+	}{
+		"fluxcd": {itemCount: 1, itemPath: "fluxcd/metadata.yaml"},
+		"velaux": {itemCount: 1, itemPath: "velaux/template.cue"},
+	}
+
+	for name, expected := range expectedAddons {
+		t.Run(name, func(t *testing.T) {
+			addon, ok := meta[name]
+			assert.True(t, ok, "addon not found in result")
+			assert.Equal(t, name, addon.Name)
+			assert.Equal(t, expected.itemCount, len(addon.Items))
+			assert.Equal(t, expected.itemPath, addon.Items[0].GetPath())
+		})
+	}
+}
+
+func TestGitlabReader_Getters(t *testing.T) {
+	t.Run("GetRef", func(t *testing.T) {
+		githWithRef := &gitlabHelper{Meta: &utils.Content{GitlabContent: utils.GitlabContent{Ref: "develop"}}}
+		rWithRef := &gitlabReader{h: githWithRef}
+		assert.Equal(t, "develop", rWithRef.GetRef())
+
+		githWithoutRef := &gitlabHelper{Meta: &utils.Content{GitlabContent: utils.GitlabContent{Ref: ""}}}
+		rWithoutRef := &gitlabReader{h: githWithoutRef}
+		assert.Equal(t, "master", rWithoutRef.GetRef())
+	})
+
+	t.Run("GetProjectID and GetProjectPath", func(t *testing.T) {
+		gith := &gitlabHelper{
+			Meta: &utils.Content{GitlabContent: utils.GitlabContent{
+				PId:  12345,
+				Path: "my/project/path",
+			}},
+		}
+		r := &gitlabReader{h: gith}
+		assert.Equal(t, 12345, r.GetProjectID())
+		assert.Equal(t, "my/project/path", r.GetProjectPath())
+	})
+
+	t.Run("RelativePath", func(t *testing.T) {
+		r := &gitlabReader{}
+		item := &GitLabItem{
+			basePath: "addons",
+			path:     "addons/fluxcd/metadata.yaml",
+		}
+		assert.Equal(t, "fluxcd/metadata.yaml", r.RelativePath(item))
+	})
+}
+
+func TestGitLabItem(t *testing.T) {
+	t.Run("Getters", func(t *testing.T) {
+		item := GitLabItem{
+			tp:   "blob",
+			name: "metadata.yaml",
+		}
+		assert.Equal(t, "blob", item.GetType())
+		assert.Equal(t, "metadata.yaml", item.GetName())
+	})
+
+	t.Run("GetPath", func(t *testing.T) {
+		testCases := map[string]struct {
+			basePath string
+			fullPath string
+			expected string
+		}{
+			"no base path": {
+				basePath: "",
+				fullPath: "fluxcd/metadata.yaml",
+				expected: "fluxcd/metadata.yaml",
+			},
+			"with base path": {
+				basePath: "addons",
+				fullPath: "addons/fluxcd/metadata.yaml",
+				expected: "fluxcd/metadata.yaml",
+			},
+		}
+		for name, tc := range testCases {
+			t.Run(name, func(t *testing.T) {
+				item := GitLabItem{basePath: tc.basePath, path: tc.fullPath}
+				assert.Equal(t, tc.expected, item.GetPath())
+			})
+		}
+	})
 }
diff --git a/pkg/addon/reader_local_test.go b/pkg/addon/reader_local_test.go
index 909fa282..83226603 100644
--- a/pkg/addon/reader_local_test.go
+++ b/pkg/addon/reader_local_test.go
@@ -17,6 +17,7 @@ limitations under the License.
 package addon
 
 import (
+	"path/filepath"
 	"strings"
 	"testing"
 
@@ -25,17 +26,66 @@ import (
 
 func TestLocalReader(t *testing.T) {
 	r := localReader{name: "local", dir: "./testdata/local"}
-	m, err := r.ListAddonMeta()
-	assert.NoError(t, err)
-	assert.Equal(t, len(m["local"].Items), 2)
 
-	file, err := r.ReadFile("metadata.yaml")
-	assert.NoError(t, err)
-	assert.Equal(t, file, metaFile)
+	t.Run("ListAddonMeta", func(t *testing.T) {
+		m, err := r.ListAddonMeta()
+		assert.NoError(t, err)
+		assert.NotNil(t, m["local"])
+		assert.Equal(t, 2, len(m["local"].Items))
 
-	file, err = r.ReadFile("resources/parameter.cue")
-	assert.NoError(t, err)
-	assert.Equal(t, true, strings.Contains(file, parameterFile))
+		// Check that the correct files are found, regardless of order.
+		foundPaths := make(map[string]bool)
+		for _, item := range m["local"].Items {
+			// Normalize path separators for consistent checking
+			foundPaths[filepath.ToSlash(item.GetPath())] = true
+		}
+		assert.True(t, foundPaths[filepath.ToSlash("testdata/local/metadata.yaml")])
+		assert.True(t, foundPaths[filepath.ToSlash("testdata/local/resources/parameter.cue")])
+	})
+
+	t.Run("ReadFile", func(t *testing.T) {
+		t.Run("read root file", func(t *testing.T) {
+			file, err := r.ReadFile("metadata.yaml")
+			assert.NoError(t, err)
+			assert.Equal(t, file, metaFile)
+		})
+		t.Run("read nested file", func(t *testing.T) {
+			file, err := r.ReadFile("resources/parameter.cue")
+			assert.NoError(t, err)
+			assert.True(t, strings.Contains(file, parameterFile))
+		})
+	})
+}
+
+func TestLocalReader_RelativePath(t *testing.T) {
+	testCases := map[string]struct {
+		dir       string
+		addonName string
+		itemPath  string
+		expected  string
+	}{
+		"item in root": {
+			dir:       "./testdata/local",
+			addonName: "my-addon",
+			itemPath:  filepath.Join("./testdata/local", "metadata.yaml"),
+			expected:  filepath.Join("my-addon", "metadata.yaml"),
+		},
+		"item in subdirectory": {
+			dir:       "./testdata/local",
+			addonName: "my-addon",
+			itemPath:  filepath.Join("./testdata/local", "resources", "parameter.cue"),
+			expected:  filepath.Join("my-addon", "resources", "parameter.cue"),
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			r := localReader{name: tc.addonName, dir: tc.dir}
+			item := OSSItem{path: tc.itemPath}
+			result := r.RelativePath(item)
+			assert.Equal(t, tc.expected, result)
+		})
+	}
 }
 
 const (
diff --git a/pkg/addon/reader_memory_test.go b/pkg/addon/reader_memory_test.go
new file mode 100644
index 00000000..19289052
--- /dev/null
+++ b/pkg/addon/reader_memory_test.go
@@ -0,0 +1,116 @@
+/*
+Copyright 2021 The KubeVela Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package addon
+
+import (
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"helm.sh/helm/v3/pkg/chart/loader"
+)
+
+var files = []*loader.BufferedFile{
+	{
+		Name: "metadata.yaml",
+		Data: []byte(`name: test-helm-addon
+version: 1.0.0
+description: This is a addon for test when install addon from helm repo
+icon: https://www.terraform.io/assets/images/logo-text-8c3ba8a6.svg
+url: https://terraform.io/
+
+tags: []
+
+deployTo:
+  control_plane: true
+  runtime_cluster: false
+
+dependencies: []
+
+invisible: false`),
+	},
+	{
+		Name: "resources/parameter.cue",
+		Data: []byte(`parameter: {
+	// test wrong parameter
+	example: *"default"
+}`),
+	},
+}
+
+func TestMemoryReader(t *testing.T) {
+	m := MemoryReader{
+		Name:  "fluxcd",
+		Files: files,
+	}
+
+	t.Run("ListAddonMeta", func(t *testing.T) {
+		meta, err := m.ListAddonMeta()
+		assert.NoError(t, err)
+		assert.Equal(t, 2, len(meta["fluxcd"].Items))
+		// Verify the internal fileData map was populated
+		_, metadataExists := m.fileData["metadata.yaml"]
+		assert.True(t, metadataExists, "metadata.yaml should exist in fileData map")
+		_, parameterExists := m.fileData["resources/parameter.cue"]
+		assert.True(t, parameterExists, "resources/parameter.cue should exist in fileData map")
+	})
+
+	t.Run("ReadFile", func(t *testing.T) {
+		// Ensure ListAddonMeta has been called to populate the internal map
+		_, _ = m.ListAddonMeta()
+
+		t.Run("read by exact name", func(t *testing.T) {
+			metaFile, err := m.ReadFile("metadata.yaml")
+			assert.NoError(t, err)
+			assert.NotEmpty(t, metaFile)
+		})
+
+		t.Run("read by prefixed name", func(t *testing.T) {
+			parameterData, err := m.ReadFile("fluxcd/resources/parameter.cue")
+			assert.NoError(t, err)
+			assert.NotEmpty(t, parameterData)
+		})
+	})
+}
+
+func TestMemoryReader_RelativePath(t *testing.T) {
+	testCases := map[string]struct {
+		addonName string
+		itemName  string
+		expected  string
+	}{
+		"name without prefix": {
+			addonName: "my-addon",
+			itemName:  "metadata.yaml",
+			expected:  filepath.Join("my-addon", "metadata.yaml"),
+		},
+		"name with prefix": {
+			addonName: "my-addon",
+			itemName:  "my-addon/template.cue",
+			expected:  "my-addon/template.cue",
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			r := MemoryReader{Name: tc.addonName}
+			item := OSSItem{name: tc.itemName}
+			result := r.RelativePath(item)
+			assert.Equal(t, tc.expected, result)
+		})
+	}
+}
diff --git a/pkg/addon/source_test.go b/pkg/addon/source_test.go
index c1033eda..c344a5a7 100644
--- a/pkg/addon/source_test.go
+++ b/pkg/addon/source_test.go
@@ -22,6 +22,127 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+// mockItem implements the Item interface for testing
+type mockItem struct {
+	path     string
+	name     string
+	typeName string
+}
+
+func (m mockItem) GetType() string { return m.typeName }
+func (m mockItem) GetPath() string { return m.path }
+func (m mockItem) GetName() string { return m.name }
+
+// mockReader implements the AsyncReader interface for testing
+type mockReader struct{}
+
+func (m mockReader) ListAddonMeta() (map[string]SourceMeta, error) { return nil, nil }
+func (m mockReader) ReadFile(path string) (string, error)          { return "", nil }
+func (m mockReader) RelativePath(item Item) string                 { return item.GetPath() }
+
+func TestClassifyItemByPattern(t *testing.T) {
+	addonName := "my-addon"
+	meta := &SourceMeta{
+		Name: addonName,
+		Items: []Item{
+			mockItem{path: "my-addon/metadata.yaml"},
+			mockItem{path: "my-addon/template.cue"},
+			mockItem{path: "my-addon/definitions/def.cue"},
+			mockItem{path: "my-addon/resources/res.yaml"},
+			mockItem{path: "my-addon/schemas/schema.cue"},
+			mockItem{path: "my-addon/views/view.cue"},
+			mockItem{path: "my-addon/some-other-file.txt"}, // Should be ignored
+		},
+	}
+
+	r := mockReader{}
+	classified := ClassifyItemByPattern(meta, r)
+
+	assert.Contains(t, classified, MetadataFileName)
+	assert.Len(t, classified[MetadataFileName], 1)
+
+	assert.Contains(t, classified, AppTemplateCueFileName)
+	assert.Len(t, classified[AppTemplateCueFileName], 1)
+
+	assert.Contains(t, classified, DefinitionsDirName)
+	assert.Len(t, classified[DefinitionsDirName], 1)
+
+	assert.Contains(t, classified, ResourcesDirName)
+	assert.Len(t, classified[ResourcesDirName], 1)
+
+	assert.Contains(t, classified, DefSchemaName)
+	assert.Len(t, classified[DefSchemaName], 1)
+
+	assert.Contains(t, classified, ViewDirName)
+	assert.Len(t, classified[ViewDirName], 1)
+
+	assert.NotContains(t, classified, "some-other-file.txt")
+}
+
+func TestNewAsyncReader(t *testing.T) {
+	testCases := map[string]struct {
+		baseURL  string
+		bucket   string
+		repo     string
+		subPath  string
+		token    string
+		rdType   ReaderType
+		wantType interface{}
+		wantErr  bool
+	}{
+		"git type": {
+			baseURL:  "https://github.com/kubevela/catalog",
+			subPath:  "addons",
+			rdType:   gitType,
+			wantType: &gitReader{},
+			wantErr:  false,
+		},
+		"gitee type": {
+			baseURL:  "https://gitee.com/kubevela/catalog",
+			subPath:  "addons",
+			rdType:   giteeType,
+			wantType: &giteeReader{},
+			wantErr:  false,
+		},
+		"oss type": {
+			baseURL:  "oss-cn-hangzhou.aliyuncs.com",
+			bucket:   "kubevela-addons",
+			rdType:   ossType,
+			wantType: &ossReader{},
+			wantErr:  false,
+		},
+		"invalid url": {
+			baseURL: "://invalid-url",
+			rdType:  gitType,
+			wantErr: true,
+		},
+		"invalid type": {
+			baseURL: "https://github.com/kubevela/catalog",
+			rdType:  "invalid",
+			wantErr: true,
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			// Note: This test does not cover the gitlab case as it requires a live API call
+			// or a complex mock setup, which is beyond the scope of this unit test.
+			if tc.rdType == gitlabType {
+				t.Skip("Skipping gitlab test in this unit test suite.")
+			}
+
+			reader, err := NewAsyncReader(tc.baseURL, tc.bucket, tc.repo, tc.subPath, tc.token, tc.rdType)
+
+			if tc.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.NoError(t, err)
+				assert.IsType(t, tc.wantType, reader)
+			}
+		})
+	}
+}
+
 func TestPathWithParent(t *testing.T) {
 	testCases := []struct {
 		readPath       string
diff --git a/pkg/addon/utils_test.go b/pkg/addon/utils_test.go
index e1c462d1..673f0ac7 100644
--- a/pkg/addon/utils_test.go
+++ b/pkg/addon/utils_test.go
@@ -26,6 +26,7 @@ import (
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 	"github.com/stretchr/testify/assert"
+	"helm.sh/helm/v3/pkg/chart"
 	"helm.sh/helm/v3/pkg/chartutil"
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -402,6 +403,90 @@ func TestCheckAddonPackageValid(t *testing.T) {
 	}
 }
 
+func TestIsRegistryFuncs(t *testing.T) {
+	t.Run("IsLocalRegistry", func(t *testing.T) {
+		assert.True(t, IsLocalRegistry(Registry{Name: "local"}))
+		assert.False(t, IsLocalRegistry(Registry{Name: "KubeVela"}))
+	})
+	t.Run("IsVersionRegistry", func(t *testing.T) {
+		assert.True(t, IsVersionRegistry(Registry{Helm: &HelmSource{}}))
+		assert.False(t, IsVersionRegistry(Registry{Git: &GitAddonSource{}}))
+		assert.False(t, IsVersionRegistry(Registry{}))
+	})
+}
+
+func TestInstallOptions(t *testing.T) {
+	t.Run("SkipValidateVersion", func(t *testing.T) {
+		installer := &Installer{}
+		SkipValidateVersion(installer)
+		assert.True(t, installer.skipVersionValidate)
+	})
+	t.Run("DryRunAddon", func(t *testing.T) {
+		installer := &Installer{}
+		DryRunAddon(installer)
+		assert.True(t, installer.dryRun)
+	})
+	t.Run("OverrideDefinitions", func(t *testing.T) {
+		installer := &Installer{}
+		OverrideDefinitions(installer)
+		assert.True(t, installer.overrideDefs)
+	})
+}
+
+func TestProduceDefConflictError(t *testing.T) {
+	t.Run("no conflicts", func(t *testing.T) {
+		err := produceDefConflictError(map[string]string{})
+		assert.NoError(t, err)
+	})
+	t.Run("with conflicts", func(t *testing.T) {
+		conflicts := map[string]string{
+			"def1": "error message 1",
+			"def2": "error message 2",
+		}
+		err := produceDefConflictError(conflicts)
+		assert.Error(t, err)
+		assert.Contains(t, err.Error(), "error message 1")
+		assert.Contains(t, err.Error(), "error message 2")
+		assert.Contains(t, err.Error(), "--override-definitions")
+	})
+}
+
+func TestGenerateChartMetadata(t *testing.T) {
+	addonDir := t.TempDir()
+
+	// Corrected YAML content with no leading whitespace
+	metaFileContent := `name: my-addon
+version: 1.2.3
+description: my addon description
+icon: http://my-icon.com
+url: http://my-home.com
+tags:
+  - tag1
+  - tag2
+system:
+  vela: ">=1.5.0"
+  kubernetes: "1.20.0"
+`
+	err := os.WriteFile(filepath.Join(addonDir, MetadataFileName), []byte(metaFileContent), 0644)
+	assert.NoError(t, err)
+
+	chartMeta, err := generateChartMetadata(addonDir)
+	assert.NoError(t, err)
+	assert.NotNil(t, chartMeta)
+
+	assert.Equal(t, "my-addon", chartMeta.Name)
+	assert.Equal(t, "1.2.3", chartMeta.Version)
+	assert.Equal(t, "my addon description", chartMeta.Description)
+	assert.Equal(t, "library", chartMeta.Type)
+	assert.Equal(t, chart.APIVersionV2, chartMeta.APIVersion)
+	assert.Equal(t, "http://my-icon.com", chartMeta.Icon)
+	assert.Equal(t, "http://my-home.com", chartMeta.Home)
+	assert.Equal(t, []string{"tag1", "tag2"}, chartMeta.Keywords)
+	assert.Equal(t, ">=1.5.0", chartMeta.Annotations[velaSystemRequirement])
+	assert.Equal(t, "1.20.0", chartMeta.Annotations[kubernetesSystemRequirement])
+	assert.Equal(t, "my-addon", chartMeta.Annotations[addonSystemRequirement])
+}
+
 const (
 	compDefYaml = `
 apiVersion: core.oam.dev/v1beta1
diff --git a/pkg/addon/versioned_registry_test.go b/pkg/addon/versioned_registry_test.go
index 34abd314..a15f8e67 100644
--- a/pkg/addon/versioned_registry_test.go
+++ b/pkg/addon/versioned_registry_test.go
@@ -17,6 +17,7 @@ limitations under the License.
 package addon
 
 import (
+	"context"
 	"encoding/base64"
 	"net/http"
 	"net/http/httptest"
@@ -28,6 +29,7 @@ import (
 	"helm.sh/helm/v3/pkg/repo"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 
 	"github.com/oam-dev/kubevela/pkg/utils/common"
 	"github.com/oam-dev/kubevela/pkg/utils/helm"
@@ -227,3 +229,159 @@ func TestToVersionedRegistry(t *testing.T) {
 	assert.EqualError(t, err, "registry 'git-based-registry' is not a versioned registry")
 	assert.Nil(t, actual)
 }
+
+func TestResolveAddonListFromIndex(t *testing.T) {
+	r := &versionedRegistry{name: "test-repo"}
+	indexFile := &repo.IndexFile{
+		Entries: map[string]repo.ChartVersions{
+			"addon-good": {
+				{Metadata: &chart.Metadata{Name: "addon-good", Version: "1.0.0", Description: "old desc", Icon: "old_icon", Keywords: []string{"tag1"}}},
+				{Metadata: &chart.Metadata{Name: "addon-good", Version: "1.2.0", Description: "latest desc", Icon: "latest_icon", Keywords: []string{"tag2"}}},
+				{Metadata: &chart.Metadata{Name: "addon-good", Version: "1.1.0", Description: "middle desc", Icon: "middle_icon", Keywords: []string{"tag3"}}},
+			},
+			"addon-empty": {},
+			"addon-single": {
+				{Metadata: &chart.Metadata{Name: "addon-single", Version: "0.1.0", Description: "single desc"}},
+			},
+		},
+	}
+
+	result := r.resolveAddonListFromIndex(r.name, indexFile)
+
+	assert.Equal(t, 2, len(result))
+
+	var addonGood, addonSingle *UIData
+	for _, addon := range result {
+		if addon.Name == "addon-good" {
+			addonGood = addon
+		}
+		if addon.Name == "addon-single" {
+			addonSingle = addon
+		}
+	}
+
+	require.NotNil(t, addonGood)
+	assert.Equal(t, "addon-good", addonGood.Name)
+	assert.Equal(t, "test-repo", addonGood.RegistryName)
+	assert.Equal(t, "1.2.0", addonGood.Version)
+	assert.Equal(t, "latest desc", addonGood.Description)
+	assert.Equal(t, "latest_icon", addonGood.Icon)
+	assert.Equal(t, []string{"tag2"}, addonGood.Tags)
+	assert.Equal(t, []string{"1.2.0", "1.1.0", "1.0.0"}, addonGood.AvailableVersions)
+
+	require.NotNil(t, addonSingle)
+	assert.Equal(t, "addon-single", addonSingle.Name)
+	assert.Equal(t, "0.1.0", addonSingle.Version)
+	assert.Equal(t, []string{"0.1.0"}, addonSingle.AvailableVersions)
+}
+
+// setupAddonTestServer creates a mock HTTP server for testing addon loading.
+// It can simulate success, 404 errors, or serving corrupt data based on the handlerType.
+func setupAddonTestServer(t *testing.T, handlerType string) string {
+	var server *httptest.Server
+	// This handler rewrites URLs in the index file to point to the server it's running on.
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		if strings.Contains(r.URL.Path, "index.yaml") {
+			content, err := os.ReadFile("./testdata/multiversion-helm-repo/index.yaml")
+			assert.NoError(t, err)
+			newContent := strings.ReplaceAll(string(content), "http://127.0.0.1:18083/multi", server.URL)
+			_, err = w.Write([]byte(newContent))
+			assert.NoError(t, err)
+			return
+		}
+
+		// After serving the index, the next request depends on the handler type.
+		switch handlerType {
+		case "success":
+			multiVersionHandler(w, r)
+		case "notfound":
+			w.WriteHeader(http.StatusNotFound)
+		case "corrupt":
+			_, err := w.Write([]byte("this is not a valid tgz file"))
+			assert.NoError(t, err)
+		default:
+			t.Errorf("unknown handler type: %s", handlerType)
+		}
+	})
+	server = httptest.NewServer(handler)
+	t.Cleanup(server.Close)
+	return server.URL
+}
+
+func TestLoadAddon(t *testing.T) {
+	testCases := []struct {
+		name           string
+		handlerType    string
+		addonName      string
+		addonVersion   string
+		expectErr      bool
+		expectedErrStr string
+		checkFunc      func(t *testing.T, pkg *WholeAddonPackage)
+	}{
+		{
+			name:         "Success case",
+			handlerType:  "success",
+			addonName:    "fluxcd",
+			addonVersion: "1.0.0",
+			expectErr:    false,
+			checkFunc: func(t *testing.T, pkg *WholeAddonPackage) {
+				assert.NotNil(t, pkg)
+				assert.Equal(t, "fluxcd", pkg.Name)
+				assert.Equal(t, "1.0.0", pkg.Version)
+				assert.NotEmpty(t, pkg.YAMLTemplates)
+				assert.Equal(t, []string{"2.0.0", "1.0.0"}, pkg.AvailableVersions)
+			},
+		},
+		{
+			name:           "Version not found",
+			handlerType:    "success",
+			addonName:      "fluxcd",
+			addonVersion:   "3.0.0",
+			expectErr:      true,
+			expectedErrStr: "specified version 3.0.0 for addon fluxcd not exist",
+		},
+		{
+			name:           "Chart download fails",
+			handlerType:    "notfound",
+			addonName:      "fluxcd",
+			addonVersion:   "1.0.0",
+			expectErr:      true,
+			expectedErrStr: ErrFetch.Error(),
+		},
+		{
+			name:           "Corrupt chart file",
+			handlerType:    "corrupt",
+			addonName:      "fluxcd",
+			addonVersion:   "1.0.0",
+			expectErr:      true,
+			expectedErrStr: ErrFetch.Error(),
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			serverURL := setupAddonTestServer(t, tc.handlerType)
+			reg := &versionedRegistry{
+				name: "test-registry",
+				url:  serverURL,
+				h:    helm.NewHelperWithCache(),
+				Opts: nil,
+			}
+
+			pkg, err := reg.loadAddon(context.Background(), tc.addonName, tc.addonVersion)
+
+			if tc.expectErr {
+				assert.Error(t, err)
+				if tc.expectedErrStr != "" {
+					assert.Contains(t, err.Error(), tc.expectedErrStr)
+				}
+			} else {
+				assert.NoError(t, err)
+			}
+
+			if tc.checkFunc != nil {
+				tc.checkFunc(t, pkg)
+			}
+		})
+	}
+}
diff --git a/pkg/appfile/appfile_test.go b/pkg/appfile/appfile_test.go
index 521a5aa9..cca5e852 100644
--- a/pkg/appfile/appfile_test.go
+++ b/pkg/appfile/appfile_test.go
@@ -19,26 +19,33 @@ package appfile
 import (
 	"context"
 	"encoding/json"
+	"strings"
 	"testing"
 
 	"cuelang.org/go/cue/cuecontext"
+	"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
 	"github.com/crossplane/crossplane-runtime/pkg/test"
 	"github.com/google/go-cmp/cmp"
 	terraformtypes "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
 	terraformapi "github.com/oam-dev/terraform-controller/api/v1beta2"
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
 	"github.com/pkg/errors"
 	"github.com/stretchr/testify/assert"
 	appsv1 "k8s.io/api/apps/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 	"sigs.k8s.io/yaml"
 
 	"github.com/kubevela/workflow/pkg/cue/model"
 
+	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
+	corev1 "k8s.io/api/core/v1"
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
 	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
 	oamtypes "github.com/oam-dev/kubevela/apis/types"
 	"github.com/oam-dev/kubevela/pkg/cue/definition"
@@ -47,8 +54,9 @@ import (
 	"github.com/oam-dev/kubevela/pkg/oam/util"
 )
 
-var _ = Describe("Test Workflow", func() {
-	It("generate workflow task runners", func() {
+func TestWorkflow(t *testing.T) {
+	k8sClient := fake.NewClientBuilder().Build()
+	t.Run("generate workflow task runners", func(t *testing.T) {
 		workflowStepDef := v1beta1.WorkflowStepDefinition{
 			Spec: v1beta1.WorkflowStepDefinitionSpec{
 				Schematic: &common.Schematic{
@@ -65,7 +73,7 @@ wait: op.#ConditionalWait & {
 		workflowStepDef.Name = "test-wait"
 		workflowStepDef.Namespace = "default"
 		err := k8sClient.Create(context.Background(), &workflowStepDef)
-		Expect(err).To(BeNil())
+		assert.NoError(t, err)
 
 		notCueStepDef := v1beta1.WorkflowStepDefinition{
 			Spec: v1beta1.WorkflowStepDefinitionSpec{
@@ -76,12 +84,12 @@ wait: op.#ConditionalWait & {
 		notCueStepDef.Name = "not-cue"
 		notCueStepDef.Namespace = "default"
 		err = k8sClient.Create(context.Background(), &notCueStepDef)
-		Expect(err).To(BeNil())
+		assert.NoError(t, err)
 	})
-})
+}
 
-var _ = Describe("Test Terraform schematic appfile", func() {
-	It("workload capability is Terraform", func() {
+func TestTerraformSchematicAppfile(t *testing.T) {
+	t.Run("workload capability is Terraform", func(t *testing.T) {
 		var (
 			ns            = "default"
 			compName      = "sample-db"
@@ -202,10 +210,10 @@ variable "password" {
 
 		comps, err := af.GenerateComponentManifests()
 		diff := cmp.Diff(comps[0], expectCompManifest)
-		Expect(diff).ShouldNot(BeEmpty())
-		Expect(err).Should(BeNil())
+		assert.NotEmpty(t, diff)
+		assert.NoError(t, err)
 	})
-})
+}
 
 func TestSetParameterValuesToKubeObj(t *testing.T) {
 	tests := map[string]struct {
@@ -301,18 +309,20 @@ func TestSetParameterValuesToKubeObj(t *testing.T) {
 			obj := tc.obj.DeepCopy()
 			err := setParameterValuesToKubeObj(obj, tc.values)
 			if diff := cmp.Diff(tc.wantObj, *obj); diff != "" {
-				t.Errorf("\nsetParameterValuesToKubeObj(...)error -want +get \nreason:%s\n%s\n", tc.reason, diff)
+				t.Errorf(
+					"\nsetParameterValuesToKubeObj(...)error -want +get \nreason:%s\n%s\n", tc.reason, diff)
 			}
 			if diff := cmp.Diff(tc.wantErr, err, test.EquateErrors()); diff != "" {
-				t.Errorf("\nsetParameterValuesToKubeObj(...)error -want +get \nreason:%s\n%s\n", tc.reason, diff)
+				t.Errorf(
+					"\nsetParameterValuesToKubeObj(...)error -want +get \nreason:%s\n%s\n", tc.reason, diff)
 			}
 		})
 	}
 
 }
 
-var _ = Describe("Test evalWorkloadWithContext", func() {
-	It("workload capability is Terraform", func() {
+func TestEvalWorkloadWithContext(t *testing.T) {
+	t.Run("workload capability is Terraform", func(t *testing.T) {
 		var (
 			ns       = "default"
 			compName = "sample-db"
@@ -404,11 +414,11 @@ variable "password" {
 		}, args.wl.Name)
 		pCtx := NewBasicContext(ctxData, args.wl.Params)
 		comp, err := evalWorkloadWithContext(pCtx, args.wl, ns, args.appName)
-		Expect(comp.ComponentOutput).ShouldNot(BeNil())
-		Expect(comp.Name).Should(Equal(""))
-		Expect(err).Should(BeNil())
+		assert.NotNil(t, comp.ComponentOutput)
+		assert.Equal(t, "", comp.Name)
+		assert.NoError(t, err)
 	})
-})
+}
 
 func TestGenerateTerraformConfigurationWorkload(t *testing.T) {
 	var (
@@ -700,16 +710,16 @@ func TestBaseGenerateComponent(t *testing.T) {
 	}, wlName)
 	pContext := NewBasicContext(ctxData, nil)
 	base := `
-	apiVersion: "apps/v1"
-	kind:       "Deployment"
-	spec: {
-		template: {
-			spec: containers: [{
-				image: "nginx"
-			}]
+		apiVersion: "apps/v1"
+		kind:       "Deployment"
+		spec: {
+			template: {
+				spec: containers: [{
+					image: "nginx"
+				}]
+			}
 		}
-	}
-`
+	`
 	inst := cuecontext.New().CompileString(base)
 	bs, _ := model.NewBase(inst.Value())
 	err := pContext.SetBase(bs)
@@ -738,8 +748,8 @@ if context.componentType == "stateless" {
 	assert.Equal(t, cm.ComponentOutputsAndTraits[0].Object["publishVersion"], publishVersion)
 }
 
-var _ = Describe("Test use context.appLabels& context.appAnnotations in componentDefinition ", func() {
-	It("Test generate AppConfig resources from ", func() {
+func TestAppLabelsAndAnnotationsInComponentDefinition(t *testing.T) {
+	t.Run("generate AppConfig resources", func(t *testing.T) {
 		af := &Appfile{
 			Name:      "app",
 			Namespace: "ns",
@@ -769,7 +779,6 @@ var _ = Describe("Test use context.appLabels& context.appAnnotations in componen
 								selector: matchLabels: {
 									"app.oam.dev/component": context.name
 								}
-						  
 								template: {
 									metadata: {
 										labels: {
@@ -783,47 +792,971 @@ var _ = Describe("Test use context.appLabels& context.appAnnotations in componen
 											}
 										}
 									}
-						  
 									spec: {
-										containers: [{
+										containers: [{ 
 											name:  context.name
 											image: parameter.image
-						  
 											if parameter["cmd"] != _|_ {
 												command: parameter.cmd
 											}
 										}]
 									}
 								}
-						  
 								selector:
 									matchLabels:
 										"app.oam.dev/component": context.name
 							}
 						  }
-						  
-						  parameter: {
+						  parameter: { 
 							// +usage=Which image would you like to use for your service
 							// +short=i
 							image: string
-						  
 							cmd?: [...string]
 						  }`},
 				},
 			},
 		}
-		By("Generate ComponentManifests")
+
+		// Generate ComponentManifests
 		componentManifests, err := af.GenerateComponentManifests()
-		Expect(err).To(BeNil())
-		By("Verify expected ComponentManifest")
+		if err != nil {
+			t.Fatalf("GenerateComponentManifests() error = %v", err)
+		}
+
+		// Verify expected ComponentManifest
 		deployment := &appsv1.Deployment{}
-		runtime.DefaultUnstructuredConverter.FromUnstructured(componentManifests[0].ComponentOutput.Object, deployment)
+		if err := runtime.DefaultUnstructuredConverter.FromUnstructured(
+			componentManifests[0].ComponentOutput.Object, deployment); err != nil {
+			t.Fatalf("Failed to convert to Deployment: %v", err)
+		}
+
 		labels := deployment.Spec.Template.Labels
 		annotations := deployment.Spec.Template.Annotations
-		Expect(cmp.Diff(len(labels), 2)).Should(BeEmpty())
-		Expect(cmp.Diff(len(annotations), 2)).Should(BeEmpty())
-		Expect(cmp.Diff(labels["lk1"], "lv1")).Should(BeEmpty())
-		Expect(cmp.Diff(annotations["ak1"], "av1")).Should(BeEmpty())
+
+		if diff := cmp.Diff(len(labels), 2); diff != "" {
+			t.Errorf("labels length mismatch (-want +got): %s", diff)
+		}
+		if diff := cmp.Diff(len(annotations), 2); diff != "" {
+			t.Errorf("annotations length mismatch (-want +got): %s", diff)
+		}
+		if diff := cmp.Diff(labels["lk1"], "lv1"); diff != "" {
+			t.Errorf("label mismatch (-want +got): %s", diff)
+		}
+		if diff := cmp.Diff(annotations["ak1"], "av1"); diff != "" {
+			t.Errorf("annotation mismatch (-want +got): %s", diff)
+		}
+	})
+}
+
+func TestIsNotFoundInAppFile(t *testing.T) {
+	tests := map[string]struct {
+		err  error
+		want bool
+	}{
+		"ErrorIsNil": {
+			err:  nil,
+			want: false,
+		},
+		"ErrorContainsSubstring": {
+			err:  errors.New("some error not found in appfile"),
+			want: true,
+		},
+		"ErrorNotContainsSubstring": {
+			err:  errors.New("some other error"),
+			want: false,
+		},
+	}
+
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			got := IsNotFoundInAppFile(tc.err)
+			assert.Equal(t, tc.want, got)
+		})
+	}
+}
+
+func TestPrepareProcessContext(t *testing.T) {
+	ctxData := process.ContextData{
+		AppName:   "test-app",
+		Namespace: "test-ns",
+	}
+
+	t.Run("Success case with nil initial context", func(t *testing.T) {
+		comp := &Component{
+			engine: definition.NewWorkloadAbstractEngine("test"),
+			FullTemplate: &Template{
+				TemplateStr: `
+output: {}
+parameter: {name: string}
+`,
+			},
+			Params: map[string]interface{}{"name": "test-name"},
+		}
+		assert.Nil(t, comp.Ctx)
+
+		ctx, err := PrepareProcessContext(comp, ctxData)
+
+		assert.NoError(t, err)
+		assert.NotNil(t, ctx)
+		assert.NotNil(t, comp.Ctx)
+		assert.Equal(t, ctx, comp.Ctx)
+	})
+
+	t.Run("Error case from invalid CUE template", func(t *testing.T) {
+		comp := &Component{
+			engine: definition.NewWorkloadAbstractEngine("test"),
+			FullTemplate: &Template{
+				TemplateStr: `
+output: {}
+parameter: {name: string & int}
+`,
+			},
+			Params: map[string]interface{}{"name": "test-name"},
+		}
+
+		_, err := PrepareProcessContext(comp, ctxData)
+
+		assert.Error(t, err)
+		assert.ErrorContains(t, err, "conflicting values string and int")
 	})
 
-})
+	t.Run("Use existing context if not nil", func(t *testing.T) {
+		comp := &Component{
+			engine: definition.NewWorkloadAbstractEngine("test"),
+			FullTemplate: &Template{
+				TemplateStr: `
+output: {}
+parameter: {name: string}
+`,
+			},
+			Params: map[string]interface{}{"name": "test-name"},
+		}
+
+		// First call to populate context
+		ctx1, err := PrepareProcessContext(comp, ctxData)
+		assert.NoError(t, err)
+		assert.NotNil(t, ctx1)
+
+		// Second call
+		ctx2, err := PrepareProcessContext(comp, ctxData)
+		assert.NoError(t, err)
+		assert.NotNil(t, ctx2)
+
+		// Check if the context object is the same
+		assert.Same(t, ctx1, ctx2)
+	})
+}
+
+func TestLoadDynamicComponent(t *testing.T) {
+	ctx := context.Background()
+	cli := fake.NewClientBuilder().Build()
+
+	t.Run("Non-ref-objects component", func(t *testing.T) {
+		af := &Appfile{}
+		comp := &common.ApplicationComponent{
+			Type: "webservice",
+		}
+		resultComp, err := af.LoadDynamicComponent(ctx, cli, comp)
+		assert.NoError(t, err)
+		assert.Equal(t, comp, resultComp)
+	})
+
+	t.Run("Invalid properties", func(t *testing.T) {
+		af := &Appfile{}
+		comp := &common.ApplicationComponent{
+			Type: v1alpha1.RefObjectsComponentType,
+			Properties: &runtime.RawExtension{
+				Raw: []byte(`{"objects": "not-an-array"}`),
+			},
+		}
+		_, err := af.LoadDynamicComponent(ctx, cli, comp)
+		assert.Error(t, err)
+		assert.Contains(t, err.Error(), "invalid ref-objects component properties")
+	})
+
+	t.Run("Success with URL selectors", func(t *testing.T) {
+		objWithURL := &unstructured.Unstructured{
+			Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "Secret",
+				"metadata": map[string]interface{}{
+					"name":      "secret1",
+					"namespace": "default",
+					"annotations": map[string]interface{}{
+						oam.AnnotationResourceURL: "http://example.com/secret.yaml",
+					},
+				},
+			},
+		}
+		af := &Appfile{
+			Namespace:       "default",
+			ReferredObjects: []*unstructured.Unstructured{objWithURL},
+			app:             &v1beta1.Application{},
+		}
+		comp := &common.ApplicationComponent{
+			Name: "my-comp",
+			Type: v1alpha1.RefObjectsComponentType,
+			Properties: &runtime.RawExtension{
+				Raw: []byte(`{"urls": ["http://example.com/secret.yaml"]}`),
+			},
+		}
+
+		resultComp, err := af.LoadDynamicComponent(ctx, cli, comp)
+		assert.NoError(t, err)
+
+		refObjList := &common.ReferredObjectList{}
+		err = json.Unmarshal(resultComp.Properties.Raw, refObjList)
+		assert.NoError(t, err)
+		assert.Len(t, refObjList.Objects, 1)
+
+		u := &unstructured.Unstructured{}
+		err = u.UnmarshalJSON(refObjList.Objects[0].Raw)
+		assert.NoError(t, err)
+		assert.Equal(t, "Secret", u.GetKind())
+		assert.Equal(t, "secret1", u.GetName())
+	})
+}
+
+func TestPolicyClient(t *testing.T) {
+	ctx := context.Background()
+	policy := &v1alpha1.Policy{
+		ObjectMeta: metav1.ObjectMeta{Name: "my-policy", Namespace: "default"},
+		Type:       "override",
+	}
+	policyKey := client.ObjectKeyFromObject(policy)
+
+	cm := &corev1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{Name: "my-cm", Namespace: "default"},
+		Data:       map[string]string{"key": "value"},
+	}
+	cmKey := client.ObjectKeyFromObject(cm)
+
+	t.Run("With AppRevision, policy in cache", func(t *testing.T) {
+		af := &Appfile{
+			AppRevision: &v1beta1.ApplicationRevision{},
+			ExternalPolicies: map[string]*v1alpha1.Policy{
+				policyKey.String(): policy,
+			},
+		}
+		cli := fake.NewClientBuilder().Build()
+		policyClient := af.PolicyClient(cli)
+
+		retrievedPolicy := &v1alpha1.Policy{}
+		err := policyClient.Get(ctx, policyKey, retrievedPolicy)
+		assert.NoError(t, err)
+		assert.Equal(t, policy, retrievedPolicy)
+	})
+
+	t.Run("With AppRevision, policy not in cache", func(t *testing.T) {
+		af := &Appfile{
+			AppRevision:      &v1beta1.ApplicationRevision{},
+			ExternalPolicies: map[string]*v1alpha1.Policy{},
+		}
+		// The policy exists in the client, but it should not be returned
+		cli := fake.NewClientBuilder().WithObjects(policy).Build()
+		policyClient := af.PolicyClient(cli)
+
+		retrievedPolicy := &v1alpha1.Policy{}
+		err := policyClient.Get(ctx, policyKey, retrievedPolicy)
+		assert.Error(t, err)
+		assert.True(t, kerrors.IsNotFound(err))
+	})
+
+	t.Run("With AppRevision, get non-policy object", func(t *testing.T) {
+		af := &Appfile{
+			AppRevision: &v1beta1.ApplicationRevision{},
+		}
+		cli := fake.NewClientBuilder().WithObjects(cm).Build()
+		policyClient := af.PolicyClient(cli)
+
+		retrievedCm := &corev1.ConfigMap{}
+		err := policyClient.Get(ctx, cmKey, retrievedCm)
+		assert.NoError(t, err)
+		assert.Equal(t, cm, retrievedCm)
+	})
+
+	t.Run("Without AppRevision, policy in client", func(t *testing.T) {
+		af := &Appfile{
+			ExternalPolicies: make(map[string]*v1alpha1.Policy),
+		}
+		cli := fake.NewClientBuilder().WithObjects(policy).Build()
+		policyClient := af.PolicyClient(cli)
+
+		retrievedPolicy := &v1alpha1.Policy{}
+		err := policyClient.Get(ctx, policyKey, retrievedPolicy)
+		assert.NoError(t, err)
+		assert.Equal(t, policy, retrievedPolicy)
+
+		// Check if the policy is now cached
+		cachedPolicy, found := af.ExternalPolicies[policyKey.String()]
+		assert.True(t, found)
+		assert.Equal(t, policy, cachedPolicy)
+	})
+
+	t.Run("Without AppRevision, policy not in client", func(t *testing.T) {
+		af := &Appfile{
+			ExternalPolicies: make(map[string]*v1alpha1.Policy),
+		}
+		cli := fake.NewClientBuilder().Build()
+		policyClient := af.PolicyClient(cli)
+
+		retrievedPolicy := &v1alpha1.Policy{}
+		err := policyClient.Get(ctx, policyKey, retrievedPolicy)
+		assert.Error(t, err)
+		assert.True(t, kerrors.IsNotFound(err))
+	})
+
+	t.Run("Without AppRevision, get non-policy object", func(t *testing.T) {
+		af := &Appfile{}
+		cli := fake.NewClientBuilder().WithObjects(cm).Build()
+		policyClient := af.PolicyClient(cli)
+
+		retrievedCm := &corev1.ConfigMap{}
+		err := policyClient.Get(ctx, cmKey, retrievedCm)
+		assert.NoError(t, err)
+		assert.Equal(t, cm, retrievedCm)
+	})
+}
+
+func TestWorkflowClient(t *testing.T) {
+	ctx := context.Background()
+	workflow := &workflowv1alpha1.Workflow{
+		ObjectMeta: metav1.ObjectMeta{Name: "my-workflow", Namespace: "default"},
+	}
+	workflowKey := client.ObjectKeyFromObject(workflow)
+
+	cm := &corev1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{Name: "my-cm", Namespace: "default"},
+		Data:       map[string]string{"key": "value"},
+	}
+	cmKey := client.ObjectKeyFromObject(cm)
+
+	t.Run("With AppRevision, workflow in cache", func(t *testing.T) {
+		af := &Appfile{
+			AppRevision:      &v1beta1.ApplicationRevision{},
+			ExternalWorkflow: workflow,
+		}
+		cli := fake.NewClientBuilder().Build()
+		workflowClient := af.WorkflowClient(cli)
+
+		retrievedWorkflow := &workflowv1alpha1.Workflow{}
+		err := workflowClient.Get(ctx, workflowKey, retrievedWorkflow)
+		assert.NoError(t, err)
+		assert.Equal(t, workflow, retrievedWorkflow)
+	})
+
+	t.Run("With AppRevision, workflow not in cache", func(t *testing.T) {
+		af := &Appfile{
+			AppRevision: &v1beta1.ApplicationRevision{},
+		}
+		// The workflow exists in the client, but it should not be returned
+		cli := fake.NewClientBuilder().WithObjects(workflow).Build()
+		workflowClient := af.WorkflowClient(cli)
+
+		retrievedWorkflow := &workflowv1alpha1.Workflow{}
+		err := workflowClient.Get(ctx, workflowKey, retrievedWorkflow)
+		assert.Error(t, err)
+		assert.True(t, kerrors.IsNotFound(err))
+	})
+
+	t.Run("With AppRevision, get non-workflow object", func(t *testing.T) {
+		af := &Appfile{
+			AppRevision: &v1beta1.ApplicationRevision{},
+		}
+		cli := fake.NewClientBuilder().WithObjects(cm).Build()
+		workflowClient := af.WorkflowClient(cli)
+
+		retrievedCm := &corev1.ConfigMap{}
+		err := workflowClient.Get(ctx, cmKey, retrievedCm)
+		assert.NoError(t, err)
+		assert.Equal(t, cm, retrievedCm)
+	})
+
+	t.Run("Without AppRevision, workflow in client", func(t *testing.T) {
+		af := &Appfile{}
+		cli := fake.NewClientBuilder().WithObjects(workflow).Build()
+		workflowClient := af.WorkflowClient(cli)
+
+		retrievedWorkflow := &workflowv1alpha1.Workflow{}
+		err := workflowClient.Get(ctx, workflowKey, retrievedWorkflow)
+		assert.NoError(t, err)
+		assert.Equal(t, workflow, retrievedWorkflow)
+
+		// Check if the workflow is now cached
+		assert.Equal(t, workflow, af.ExternalWorkflow)
+	})
+
+	t.Run("Without AppRevision, workflow not in client", func(t *testing.T) {
+		af := &Appfile{}
+		cli := fake.NewClientBuilder().Build()
+		workflowClient := af.WorkflowClient(cli)
+
+		retrievedWorkflow := &workflowv1alpha1.Workflow{}
+		err := workflowClient.Get(ctx, workflowKey, retrievedWorkflow)
+		assert.Error(t, err)
+		assert.True(t, kerrors.IsNotFound(err))
+	})
+
+	t.Run("Without AppRevision, get non-workflow object", func(t *testing.T) {
+		af := &Appfile{}
+		cli := fake.NewClientBuilder().WithObjects(cm).Build()
+		workflowClient := af.WorkflowClient(cli)
+
+		retrievedCm := &corev1.ConfigMap{}
+		err := workflowClient.Get(ctx, cmKey, retrievedCm)
+		assert.NoError(t, err)
+		assert.Equal(t, cm, retrievedCm)
+	})
+}
+
+func TestSetWorkloadRefToTrait(t *testing.T) {
+	wlRef := corev1.ObjectReference{
+		APIVersion: "apps/v1",
+		Kind:       "Deployment",
+		Name:       "my-workload",
+	}
+
+	tests := map[string]struct {
+		reason    string
+		appfile   *Appfile
+		trait     *unstructured.Unstructured
+		wantTrait *unstructured.Unstructured
+		wantErr   error
+	}{
+		"AuxiliaryWorkloadTrait": {
+			reason:  "Should do nothing for auxiliary-workload traits",
+			appfile: &Appfile{},
+			trait: &unstructured.Unstructured{Object: map[string]interface{}{
+				"metadata": map[string]interface{}{
+					"labels": map[string]interface{}{
+						oam.TraitTypeLabel: definition.AuxiliaryWorkload,
+					},
+				},
+			}},
+			wantTrait: &unstructured.Unstructured{Object: map[string]interface{}{
+				"metadata": map[string]interface{}{
+					"labels": map[string]interface{}{
+						oam.TraitTypeLabel: definition.AuxiliaryWorkload,
+					},
+				},
+			}},
+			wantErr: nil,
+		},
+		"TraitDefinitionNotFound": {
+			reason: "Should return error if TraitDefinition is not found",
+			appfile: &Appfile{
+				RelatedTraitDefinitions: map[string]*v1beta1.TraitDefinition{},
+			},
+			trait: &unstructured.Unstructured{Object: map[string]interface{}{
+				"metadata": map[string]interface{}{
+					"labels": map[string]interface{}{
+						oam.TraitTypeLabel: "my-trait",
+					},
+				},
+			}},
+			wantTrait: &unstructured.Unstructured{Object: map[string]interface{}{
+				"metadata": map[string]interface{}{
+					"labels": map[string]interface{}{
+						oam.TraitTypeLabel: "my-trait",
+					},
+				},
+			}},
+			wantErr: errors.Errorf("TraitDefinition %s not found in appfile", "my-trait"),
+		},
+		"WorkloadRefPathIsEmpty": {
+			reason: "Should do nothing if workloadRefPath is empty",
+			appfile: &Appfile{
+				RelatedTraitDefinitions: map[string]*v1beta1.TraitDefinition{
+					"my-trait": {
+						Spec: v1beta1.TraitDefinitionSpec{
+							WorkloadRefPath: "",
+						},
+					},
+				},
+			},
+			trait: &unstructured.Unstructured{Object: map[string]interface{}{
+				"metadata": map[string]interface{}{
+					"labels": map[string]interface{}{
+						oam.TraitTypeLabel: "my-trait",
+					},
+				},
+			}},
+			wantTrait: &unstructured.Unstructured{Object: map[string]interface{}{
+				"metadata": map[string]interface{}{
+					"labels": map[string]interface{}{
+						oam.TraitTypeLabel: "my-trait",
+					},
+				},
+			}},
+			wantErr: nil,
+		},
+		"SetWorkloadRefSucceeds": {
+			reason: "Should set workload reference correctly",
+			appfile: &Appfile{
+				RelatedTraitDefinitions: map[string]*v1beta1.TraitDefinition{
+					"my-trait": {
+						Spec: v1beta1.TraitDefinitionSpec{
+							WorkloadRefPath: "spec.workloadRef",
+						},
+					},
+				},
+			},
+			trait: &unstructured.Unstructured{Object: map[string]interface{}{
+				"metadata": map[string]interface{}{
+					"labels": map[string]interface{}{
+						oam.TraitTypeLabel: "my-trait",
+					},
+				},
+				"spec": map[string]interface{}{},
+			}},
+			wantTrait: &unstructured.Unstructured{Object: map[string]interface{}{
+				"metadata": map[string]interface{}{
+					"labels": map[string]interface{}{
+						oam.TraitTypeLabel: "my-trait",
+					},
+				},
+				"spec": map[string]interface{}{
+					"workloadRef": map[string]interface{}{
+						"apiVersion": "apps/v1",
+						"kind":       "Deployment",
+						"name":       "my-workload",
+					},
+				},
+			}},
+			wantErr: nil,
+		},
+		"SetWorkloadRefWithSuffixedTraitType": {
+			reason: "Should find the base trait definition and set workload reference",
+			appfile: &Appfile{
+				RelatedTraitDefinitions: map[string]*v1beta1.TraitDefinition{
+					"my-trait": {
+						Spec: v1beta1.TraitDefinitionSpec{
+							WorkloadRefPath: "spec.workloadRef",
+						},
+					},
+				},
+			},
+			trait: &unstructured.Unstructured{Object: map[string]interface{}{
+				"metadata": map[string]interface{}{
+					"labels": map[string]interface{}{
+						oam.TraitTypeLabel: "my-trait-abcde",
+					},
+				},
+				"spec": map[string]interface{}{},
+			}},
+			wantTrait: &unstructured.Unstructured{Object: map[string]interface{}{
+				"metadata": map[string]interface{}{
+					"labels": map[string]interface{}{
+						oam.TraitTypeLabel: "my-trait-abcde",
+					},
+				},
+				"spec": map[string]interface{}{
+					"workloadRef": map[string]interface{}{
+						"apiVersion": "apps/v1",
+						"kind":       "Deployment",
+						"name":       "my-workload",
+					},
+				},
+			}},
+			wantErr: nil,
+		},
+		"InvalidWorkloadRefPath": {
+			reason: "Should return an error for an invalid workloadRefPath",
+			appfile: &Appfile{
+				RelatedTraitDefinitions: map[string]*v1beta1.TraitDefinition{
+					"my-trait": {
+						Spec: v1beta1.TraitDefinitionSpec{
+							WorkloadRefPath: "spec[workloadRef", // Invalid path
+						},
+					},
+				},
+			},
+			trait: &unstructured.Unstructured{Object: map[string]interface{}{
+				"metadata": map[string]interface{}{
+					"labels": map[string]interface{}{
+						oam.TraitTypeLabel: "my-trait",
+					},
+				},
+			}},
+			wantTrait: &unstructured.Unstructured{Object: map[string]interface{}{
+				"metadata": map[string]interface{}{
+					"labels": map[string]interface{}{
+						oam.TraitTypeLabel: "my-trait",
+					},
+				},
+			}},
+			wantErr: fieldpath.Pave(map[string]interface{}{}).SetValue("spec[workloadRef", wlRef),
+		},
+	}
+
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			traitCopy := tc.trait.DeepCopy()
+			err := tc.appfile.setWorkloadRefToTrait(wlRef, traitCopy)
+
+			if diff := cmp.Diff(tc.wantErr, err, test.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\nsetWorkloadRefToTrait(...): -want error, +got error:\n%s\n", tc.reason, diff)
+			}
+			if diff := cmp.Diff(tc.wantTrait, traitCopy); diff != "" {
+				t.Errorf("\n%s\nsetWorkloadRefToTrait(...): -want trait, +got trait:\n%s\n", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestSetOAMContract(t *testing.T) {
+	baseAppfile := &Appfile{
+		Name:            "test-app",
+		Namespace:       "test-ns",
+		AppRevisionName: "test-app-v1",
+		AppLabels: map[string]string{
+			"app-label": "app-label-val",
+		},
+		AppAnnotations: map[string]string{
+			"app-annot": "app-annot-val",
+		},
+		RelatedTraitDefinitions: map[string]*v1beta1.TraitDefinition{
+			"my-trait": {
+				Spec: v1beta1.TraitDefinitionSpec{
+					WorkloadRefPath: "spec.workloadRef",
+				},
+			},
+			"no-ref-trait": {
+				Spec: v1beta1.TraitDefinitionSpec{},
+			},
+		},
+	}
+
+	baseWorkload := &unstructured.Unstructured{
+		Object: map[string]interface{}{
+			"apiVersion": "apps/v1",
+			"kind":       "Deployment",
+			"metadata": map[string]interface{}{
+				"name": "my-workload",
+			},
+		},
+	}
+
+	baseTrait := &unstructured.Unstructured{
+		Object: map[string]interface{}{
+			"apiVersion": "v1",
+			"kind":       "Service",
+			"metadata": map[string]interface{}{
+				"labels": map[string]interface{}{
+					oam.TraitTypeLabel: "my-trait",
+				},
+			},
+		},
+	}
+
+	tests := map[string]struct {
+		reason   string
+		appfile  *Appfile
+		comp     *oamtypes.ComponentManifest
+		wantComp *oamtypes.ComponentManifest
+		wantErr  bool
+	}{
+		"SuccessBasic": {
+			reason:  "A basic workload and trait should be assembled correctly",
+			appfile: baseAppfile,
+			comp: &oamtypes.ComponentManifest{
+				Name:            "my-comp",
+				ComponentOutput: baseWorkload.DeepCopy(),
+				ComponentOutputsAndTraits: []*unstructured.Unstructured{
+					baseTrait.DeepCopy(),
+				},
+			},
+			wantComp: &oamtypes.ComponentManifest{
+				Name: "my-comp",
+				ComponentOutput: &unstructured.Unstructured{
+					Object: map[string]interface{}{
+						"apiVersion": "apps/v1",
+						"kind":       "Deployment",
+						"metadata": map[string]interface{}{
+							"name":      "my-workload",
+							"namespace": "test-ns",
+							"labels": map[string]interface{}{
+								"app-label":              "app-label-val",
+								oam.LabelAppName:         "test-app",
+								oam.LabelAppNamespace:    "test-ns",
+								oam.LabelAppRevision:     "test-app-v1",
+								oam.LabelAppComponent:    "my-comp",
+								oam.LabelOAMResourceType: oam.ResourceTypeWorkload,
+							},
+							"annotations": map[string]interface{}{
+								"app-annot": "app-annot-val",
+							},
+						},
+					},
+				},
+				ComponentOutputsAndTraits: []*unstructured.Unstructured{
+					{
+						Object: map[string]interface{}{
+							"apiVersion": "v1",
+							"kind":       "Service",
+							"metadata": map[string]interface{}{
+								"name":      "", // Name will be generated
+								"namespace": "test-ns",
+								"labels": map[string]interface{}{
+									"app-label":              "app-label-val",
+									oam.TraitTypeLabel:       "my-trait",
+									oam.LabelAppName:         "test-app",
+									oam.LabelAppNamespace:    "test-ns",
+									oam.LabelAppRevision:     "test-app-v1",
+									oam.LabelAppComponent:    "my-comp",
+									oam.LabelOAMResourceType: oam.ResourceTypeTrait,
+								},
+								"annotations": map[string]interface{}{
+									"app-annot": "app-annot-val",
+								},
+							},
+							"spec": map[string]interface{}{
+								"workloadRef": map[string]interface{}{
+									"apiVersion": "apps/v1",
+									"kind":       "Deployment",
+									"name":       "my-workload",
+								},
+							},
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
+	}
+
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			compCopy := deepCopyComponentManifest(tc.comp)
+			err := tc.appfile.SetOAMContract(compCopy)
+
+			if (err != nil) != tc.wantErr {
+				t.Errorf("\n%s\nSetOAMContract(...): want error? %v, got error: %v\n", tc.reason, tc.wantErr, err)
+			}
+
+			if name == "SuccessBasic" || name == "SuccessNameGeneration" {
+				for i, trait := range compCopy.ComponentOutputsAndTraits {
+					gotName := trait.GetName()
+					wantTrait := tc.wantComp.ComponentOutputsAndTraits[i]
+					traitType := trait.GetLabels()[oam.TraitTypeLabel]
+					expectedPrefix := compCopy.Name + "-" + traitType + "-"
+
+					if !strings.HasPrefix(gotName, expectedPrefix) {
+						t.Errorf("\n%s\nSetOAMContract(...): trait name %q does not have expected prefix %q", tc.reason, gotName, expectedPrefix)
+					}
+					trait.SetName("")
+					wantTrait.SetName("")
+				}
+			}
+
+			if !tc.wantErr {
+				if diff := cmp.Diff(tc.wantComp.ComponentOutput, compCopy.ComponentOutput); diff != "" {
+					t.Errorf("\n%s\nSetOAMContract(...): -want workload, +got workload:\n%s\n", tc.reason, diff)
+				}
+				if diff := cmp.Diff(tc.wantComp.ComponentOutputsAndTraits, compCopy.ComponentOutputsAndTraits); diff != "" {
+					t.Errorf("\n%s\nSetOAMContract(...): -want traits, +got traits:\n%s\n", tc.reason, diff)
+				}
+			}
+		})
+	}
+}
+
+func deepCopyComponentManifest(in *oamtypes.ComponentManifest) *oamtypes.ComponentManifest {
+	if in == nil {
+		return nil
+	}
+	out := &oamtypes.ComponentManifest{
+		Name: in.Name,
+	}
+	if in.ComponentOutput != nil {
+		out.ComponentOutput = in.ComponentOutput.DeepCopy()
+	}
+	if in.ComponentOutputsAndTraits != nil {
+		out.ComponentOutputsAndTraits = make([]*unstructured.Unstructured, len(in.ComponentOutputsAndTraits))
+		for i, trait := range in.ComponentOutputsAndTraits {
+			if trait != nil {
+				out.ComponentOutputsAndTraits[i] = trait.DeepCopy()
+			}
+		}
+	}
+	return out
+}
+
+func TestGenerateComponentManifests(t *testing.T) {
+	baseEngine := definition.NewWorkloadAbstractEngine("test-engine")
+	baseTraitEngine := definition.NewTraitAbstractEngine("scaler")
+	baseTemplate := &Template{
+		TemplateStr: `
+			output: {
+				apiVersion: "apps/v1"
+				kind:       "Deployment"
+				spec: {
+					selector: matchLabels: {
+						"app.oam.dev/component": context.name
+					}
+					template: {
+						metadata: labels: {
+							"app.oam.dev/component": context.name
+						}
+						spec: containers: [{
+							name:  context.name
+							image: parameter.image
+						}]
+					}
+				}
+			}
+			parameter: {
+				image: string
+			}`,
+	}
+	baseTraitTemplate := &Trait{
+		Name:   "scaler",
+		engine: baseTraitEngine,
+		Template: `
+			outputs: scaler: {
+				apiVersion: "autoscaling/v2beta2"
+				kind:       "HorizontalPodAutoscaler"
+				spec: {
+					scaleTargetRef: {
+						apiVersion: "apps/v1"
+						kind:       "Deployment"
+						name:       context.name
+					}
+					minReplicas: parameter.min
+					maxReplicas: parameter.max
+				}
+			}
+			parameter: {
+				min: *1 | int
+				max: *10 | int
+			}
+		`,
+		Params: map[string]interface{}{
+			"min": 2,
+			"max": 15,
+		},
+	}
+
+	type fields struct {
+		Appfile *Appfile
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		wantErr bool
+	}{
+		{
+			name: "Test generate component manifests",
+			fields: fields{
+				Appfile: &Appfile{
+					Name:            "test-app",
+					Namespace:       "test-ns",
+					AppRevisionName: "test-app-v1",
+					ParsedComponents: []*Component{
+						{
+							Name:   "test-comp",
+							Type:   "worker",
+							Params: map[string]interface{}{"image": "nginx:1.10"},
+							engine: baseEngine,
+							FullTemplate: &Template{
+								TemplateStr: baseTemplate.TemplateStr,
+							},
+							Traits: []*Trait{baseTraitTemplate},
+						},
+					},
+					RelatedTraitDefinitions: map[string]*v1beta1.TraitDefinition{
+						"scaler": {
+							Spec: v1beta1.TraitDefinitionSpec{
+								WorkloadRefPath: "spec.scaleTargetRef",
+							},
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			af := tt.fields.Appfile
+			got, err := af.GenerateComponentManifests()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("GenerateComponentManifests() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			assert.NoError(t, err)
+			assert.Equal(t, len(af.ParsedComponents), len(got))
+			assert.Equal(t, "test-comp", got[0].Name)
+			assert.Equal(t, "Deployment", got[0].ComponentOutput.GetKind())
+			assert.Equal(t, "test-comp", got[0].ComponentOutput.GetName())
+			assert.Equal(t, "test-ns", got[0].ComponentOutput.GetNamespace())
+			assert.Equal(t, "test-app", got[0].ComponentOutput.GetLabels()[oam.LabelAppName])
+			assert.Equal(t, 1, len(got[0].ComponentOutputsAndTraits))
+			trait := got[0].ComponentOutputsAndTraits[0]
+			assert.Equal(t, "HorizontalPodAutoscaler", trait.GetKind())
+			assert.True(t, strings.HasPrefix(trait.GetName(), "test-comp-scaler-"))
+			paved := fieldpath.Pave(trait.Object)
+			name, err := paved.GetString("spec.scaleTargetRef.name")
+			assert.NoError(t, err)
+			assert.Equal(t, "test-comp", name)
+			assert.Equal(t, af.Artifacts, got)
+		})
+	}
+}
+
+func TestGeneratePolicyManifests(t *testing.T) {
+	policyEngine := definition.NewWorkloadAbstractEngine("test-policy")
+	policyTemplate := &Template{
+		TemplateStr: `
+			output: {
+				apiVersion: "v1"
+				kind:       "ConfigMap"
+				metadata: {
+					name: context.name
+				}
+				data: {
+					key: parameter.value
+				}
+			}
+			parameter: {
+				value: string
+			}
+		`,
+	}
+
+	af := &Appfile{
+		Name:            "test-app",
+		Namespace:       "test-ns",
+		AppRevisionName: "test-app-v1",
+		ParsedPolicies: []*Component{
+			{
+				Name:   "test-policy",
+				Type:   "override",
+				Params: map[string]interface{}{"value": "test-value"},
+				engine: policyEngine,
+				FullTemplate: &Template{
+					TemplateStr: policyTemplate.TemplateStr,
+				},
+			},
+		},
+		AppLabels: map[string]string{
+			"label-key": "label-value",
+		},
+	}
+
+	manifests, err := af.GeneratePolicyManifests(context.Background())
+	assert.NoError(t, err)
+	assert.Equal(t, 1, len(manifests))
+
+	cm := manifests[0]
+	assert.Equal(t, "v1", cm.GetAPIVersion())
+	assert.Equal(t, "ConfigMap", cm.GetKind())
+	assert.Equal(t, "test-policy", cm.GetName())
+	assert.Equal(t, "test-ns", cm.GetNamespace())
+	assert.Equal(t, "test-app", cm.GetLabels()[oam.LabelAppName])
+
+	data, found, err := unstructured.NestedStringMap(cm.Object, "data")
+	assert.NoError(t, err)
+	assert.True(t, found)
+	assert.Equal(t, "test-value", data["key"])
+}
diff --git a/pkg/appfile/dryrun/dryrun_test.go b/pkg/appfile/dryrun/dryrun_test.go
index 870fc8ca..f97416b3 100644
--- a/pkg/appfile/dryrun/dryrun_test.go
+++ b/pkg/appfile/dryrun/dryrun_test.go
@@ -61,6 +61,20 @@ var _ = Describe("Test DryRun", func() {
 		diff := cmp.Diff(&expC, comps[0])
 		Expect(diff).Should(BeEmpty())
 	})
+
+	It("Test ValidateApp forces default namespace (validation object) but ExecuteDryRun keeps app namespace", func() {
+		err := dryrunOpt.ValidateApp(context.Background(), "./testdata/dryrun-app-with-ns.yaml")
+		Expect(err).Should(BeNil())
+		appYAML := readDataFromFile("./testdata/dryrun-app-with-ns.yaml")
+		app := &v1beta1.Application{}
+		b, err2 := yaml.YAMLToJSON([]byte(appYAML))
+		Expect(err2).Should(BeNil())
+		err2 = json.Unmarshal(b, app)
+		Expect(err2).Should(BeNil())
+		comps, _, err3 := dryrunOpt.ExecuteDryRun(context.Background(), app)
+		Expect(err3).Should(BeNil())
+		Expect(comps[0].ComponentOutput.GetNamespace()).Should(Equal("custom-ns"))
+	})
 })
 
 var _ = Describe("Test dry run with policies", func() {
diff --git a/pkg/appfile/dryrun/testdata/dryrun-app-with-ns.yaml b/pkg/appfile/dryrun/testdata/dryrun-app-with-ns.yaml
new file mode 100644
index 00000000..a983d435
--- /dev/null
+++ b/pkg/appfile/dryrun/testdata/dryrun-app-with-ns.yaml
@@ -0,0 +1,14 @@
+apiVersion: core.oam.dev/v1beta1
+kind: Application
+metadata:
+  name: app-dryrun-ns
+  namespace: custom-ns
+spec:
+  components:
+    - name: myweb
+      type: myworker
+      properties:
+        image: busybox
+        cmd:
+          - sleep
+          - "1000"
diff --git a/pkg/appfile/parser_test.go b/pkg/appfile/parser_test.go
index 36953f13..6d431b1f 100644
--- a/pkg/appfile/parser_test.go
+++ b/pkg/appfile/parser_test.go
@@ -24,8 +24,7 @@ import (
 	"testing"
 
 	"github.com/crossplane/crossplane-runtime/pkg/test"
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
+	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
 	"github.com/stretchr/testify/assert"
 	errors2 "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -34,16 +33,217 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 	"sigs.k8s.io/yaml"
 
-	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
-
 	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
-	"github.com/oam-dev/kubevela/apis/types"
-
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
+	"github.com/oam-dev/kubevela/apis/types"
 	"github.com/oam-dev/kubevela/pkg/oam/util"
 	common2 "github.com/oam-dev/kubevela/pkg/utils/common"
 )
 
+func TestParsePolicies(t *testing.T) {
+	overrideCompDef := &v1beta1.ComponentDefinition{
+		ObjectMeta: metav1.ObjectMeta{Name: "webservice", Namespace: "vela-system"},
+		Spec:       v1beta1.ComponentDefinitionSpec{Workload: common.WorkloadTypeDescriptor{Type: "Deployment"}},
+	}
+	customPolicyDef := &v1beta1.PolicyDefinition{
+		ObjectMeta: metav1.ObjectMeta{Name: "custom-policy", Namespace: "vela-system"},
+		Spec: v1beta1.PolicyDefinitionSpec{
+			Schematic: &common.Schematic{
+				CUE: &common.CUE{Template: "parameter: {name: string}"},
+			},
+		},
+	}
+	schemes := runtime.NewScheme()
+	v1beta1.AddToScheme(schemes)
+
+	testcases := []struct {
+		name           string
+		appfile        *Appfile
+		client         client.Client
+		wantErrContain string
+		assertFunc     func(*testing.T, *Appfile)
+	}{
+		{
+			name: "policy with nil properties",
+			appfile: &Appfile{
+				app: &v1beta1.Application{
+					Spec: v1beta1.ApplicationSpec{
+						Policies: []v1beta1.AppPolicy{
+							{
+								Name:       "gc-policy",
+								Type:       v1alpha1.GarbageCollectPolicyType,
+								Properties: nil,
+							},
+						},
+					},
+				},
+			},
+			client:         fake.NewClientBuilder().WithScheme(schemes).Build(),
+			wantErrContain: "must not have empty properties",
+		},
+		{
+			name: "debug policy",
+			appfile: &Appfile{
+				app: &v1beta1.Application{
+					Spec: v1beta1.ApplicationSpec{
+						Policies: []v1beta1.AppPolicy{
+							{
+								Name: "debug-policy",
+								Type: v1alpha1.DebugPolicyType,
+							},
+						},
+					},
+				},
+			},
+			client: fake.NewClientBuilder().WithScheme(schemes).Build(),
+			assertFunc: func(t *testing.T, af *Appfile) {
+				assert.True(t, af.Debug)
+			},
+		},
+		{
+			name: "override policy fails to get definition",
+			appfile: &Appfile{
+				app: &v1beta1.Application{
+					Spec: v1beta1.ApplicationSpec{
+						Components: []common.ApplicationComponent{
+							{
+								Name: "comp1",
+								Type: "webservice",
+							},
+						},
+						Policies: []v1beta1.AppPolicy{
+							{
+								Name: "override-policy",
+								Type: v1alpha1.OverridePolicyType,
+								Properties: util.Object2RawExtension(v1alpha1.OverridePolicySpec{
+									Components: []v1alpha1.EnvComponentPatch{
+										{
+											Name: "comp1",
+											Type: "webservice",
+										},
+									},
+								}),
+							},
+						},
+					},
+				},
+				RelatedComponentDefinitions: make(map[string]*v1beta1.ComponentDefinition),
+				RelatedTraitDefinitions:     make(map[string]*v1beta1.TraitDefinition),
+			},
+			client: &test.MockClient{
+				MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
+					return fmt.Errorf("get definition error")
+				},
+			},
+			wantErrContain: "get definition error",
+		},
+		{
+			name: "override policy success",
+			appfile: &Appfile{
+				app: &v1beta1.Application{
+					Spec: v1beta1.ApplicationSpec{
+						Components: []common.ApplicationComponent{
+							{
+								Name: "comp1",
+								Type: "webservice",
+							},
+						},
+						Policies: []v1beta1.AppPolicy{
+							{
+								Name: "override-policy",
+								Type: v1alpha1.OverridePolicyType,
+								Properties: util.Object2RawExtension(v1alpha1.OverridePolicySpec{
+									Components: []v1alpha1.EnvComponentPatch{
+										{
+											Name: "comp1",
+											Type: "webservice",
+										},
+									},
+								}),
+							},
+						},
+					},
+				},
+				RelatedComponentDefinitions: make(map[string]*v1beta1.ComponentDefinition),
+				RelatedTraitDefinitions:     make(map[string]*v1beta1.TraitDefinition),
+			},
+			client: fake.NewClientBuilder().WithScheme(schemes).WithObjects(overrideCompDef).Build(),
+			assertFunc: func(t *testing.T, af *Appfile) {
+				assert.Contains(t, af.RelatedComponentDefinitions, "webservice")
+			},
+		},
+		{
+			name: "custom policy definition not found",
+			appfile: &Appfile{
+				app: &v1beta1.Application{
+					Spec: v1beta1.ApplicationSpec{
+						Policies: []v1beta1.AppPolicy{
+							{
+								Name:       "my-policy",
+								Type:       "custom-policy",
+								Properties: util.Object2RawExtension(map[string]string{"name": "test"}),
+							},
+						},
+					},
+				},
+			},
+			client: &test.MockClient{
+				MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
+					if _, ok := obj.(*v1beta1.PolicyDefinition); ok {
+						return errors2.NewNotFound(v1beta1.Resource("policydefinition"), "custom-policy")
+					}
+					return nil
+				},
+			},
+			wantErrContain: "fetch component/policy type of my-policy",
+		},
+		{
+			name: "custom policy success",
+			appfile: &Appfile{
+				app: &v1beta1.Application{
+					Spec: v1beta1.ApplicationSpec{
+						Policies: []v1beta1.AppPolicy{
+							{
+								Name:       "my-policy",
+								Type:       "custom-policy",
+								Properties: util.Object2RawExtension(map[string]string{"name": "test"}),
+							},
+						},
+					},
+				},
+			},
+			client: fake.NewClientBuilder().WithScheme(schemes).WithObjects(customPolicyDef).Build(),
+			assertFunc: func(t *testing.T, af *Appfile) {
+				assert.Equal(t, 1, len(af.ParsedPolicies))
+				assert.Equal(t, "my-policy", af.ParsedPolicies[0].Name)
+				assert.Equal(t, "custom-policy", af.ParsedPolicies[0].Type)
+			},
+		},
+	}
+
+	for _, tc := range testcases {
+		t.Run(tc.name, func(t *testing.T) {
+			p := NewApplicationParser(tc.client)
+			// This function is tested separated, mock it for parsePolicies
+			if tc.appfile.app != nil {
+				tc.appfile.Policies = tc.appfile.app.Spec.Policies
+			}
+			err := p.parsePolicies(context.Background(), tc.appfile)
+
+			if tc.wantErrContain != "" {
+				assert.Error(t, err)
+				assert.Contains(t, err.Error(), tc.wantErrContain)
+			} else {
+				assert.NoError(t, err)
+				if tc.assertFunc != nil {
+					tc.assertFunc(t, tc.appfile)
+				}
+			}
+		})
+	}
+}
+
 var expectedExceptApp = &Appfile{
 	Name: "application-sample",
 	ParsedComponents: []*Component{
@@ -81,10 +281,10 @@ var expectedExceptApp = &Appfile{
       			}
       		}
       
-      		selector:
-      			matchLabels:
-      				"app.oam.dev/component": context.name
-      	}
+      			selector:
+      				matchLabels:
+      					"app.oam.dev/component": context.name
+      		}
       }
       
       parameter: {
@@ -149,7 +349,7 @@ spec:
       		selector:
       			matchLabels:
       				"app.oam.dev/component": context.name
-      	}
+      		}
       }
 
       parameter: {
@@ -235,11 +435,11 @@ spec:
       properties:
 `
 
-var _ = Describe("Test application parser", func() {
-	It("Test parse an application", func() {
+func TestApplicationParser(t *testing.T) {
+	t.Run("Test parse an application", func(t *testing.T) {
 		o := v1beta1.Application{}
 		err := yaml.Unmarshal([]byte(appfileYaml), &o)
-		Expect(err).ShouldNot(HaveOccurred())
+		assert.NoError(t, err)
 
 		// Create a mock client
 		tclient := test.MockClient{
@@ -266,24 +466,24 @@ var _ = Describe("Test application parser", func() {
 		}
 
 		appfile, err := NewApplicationParser(&tclient).GenerateAppFile(context.TODO(), &o)
-		Expect(err).ShouldNot(HaveOccurred())
-		Expect(equal(expectedExceptApp, appfile)).Should(BeTrue())
+		assert.NoError(t, err)
+		assert.True(t, equal(expectedExceptApp, appfile))
 
 		notfound := v1beta1.Application{}
 		err = yaml.Unmarshal([]byte(appfileYaml2), &notfound)
-		Expect(err).ShouldNot(HaveOccurred())
+		assert.NoError(t, err)
 		_, err = NewApplicationParser(&tclient).GenerateAppFile(context.TODO(), &notfound)
-		Expect(err).Should(HaveOccurred())
+		assert.Error(t, err)
 
-		By("app with empty policy")
+		t.Log("app with empty policy")
 		emptyPolicy := v1beta1.Application{}
 		err = yaml.Unmarshal([]byte(appfileYamlEmptyPolicy), &emptyPolicy)
-		Expect(err).ShouldNot(HaveOccurred())
+		assert.NoError(t, err)
 		_, err = NewApplicationParser(&tclient).GenerateAppFile(context.TODO(), &emptyPolicy)
-		Expect(err).Should(HaveOccurred())
-		Expect(err.Error()).Should(ContainSubstring("have empty properties"))
+		assert.Error(t, err)
+		assert.Contains(t, err.Error(), "have empty properties")
 	})
-})
+}
 
 func equal(af, dest *Appfile) bool {
 	if af.Name != dest.Name || len(af.ParsedComponents) != len(dest.ParsedComponents) {
@@ -313,29 +513,28 @@ func equal(af, dest *Appfile) bool {
 	return true
 }
 
-var _ = Describe("Test application parser", func() {
+func TestApplicationParserWithLegacyRevision(t *testing.T) {
 	var app v1beta1.Application
 	var apprev v1beta1.ApplicationRevision
 	var wsd v1beta1.WorkflowStepDefinition
 	var expectedExceptAppfile *Appfile
 	var mockClient test.MockClient
 
-	BeforeEach(func() {
-		// prepare WorkflowStepDefinition
-		Expect(common2.ReadYamlToObject("testdata/backport-1-2/wsd.yaml", &wsd)).Should(BeNil())
-
-		// prepare verify data
-		expectedExceptAppfile = &Appfile{
-			Name: "backport-1-2-test-demo",
-			ParsedComponents: []*Component{
-				{
-					Name: "backport-1-2-test-demo",
-					Type: "webservice",
-					Params: map[string]interface{}{
-						"image": "nginx",
-					},
-					FullTemplate: &Template{
-						TemplateStr: `
+	// prepare WorkflowStepDefinition
+	assert.NoError(t, common2.ReadYamlToObject("testdata/backport-1-2/wsd.yaml", &wsd))
+
+	// prepare verify data
+	expectedExceptAppfile = &Appfile{
+		Name: "backport-1-2-test-demo",
+		ParsedComponents: []*Component{
+			{
+				Name: "backport-1-2-test-demo",
+				Type: "webservice",
+				Params: map[string]interface{}{
+					"image": "nginx",
+				},
+				FullTemplate: &Template{
+					TemplateStr: `
       output: {
         apiVersion: "apps/v1"
       	kind:       "Deployment"
@@ -361,10 +560,10 @@ var _ = Describe("Test application parser", func() {
       			}
       		}
       
-      		selector:
-      			matchLabels:
-      				"app.oam.dev/component": context.name
-      	}
+      			selector:
+      				matchLabels:
+      					"app.oam.dev/component": context.name
+      		}
       }
       
       parameter: {
@@ -374,14 +573,14 @@ var _ = Describe("Test application parser", func() {
       
       	cmd?: [...string]
       }`,
-					},
-					Traits: []*Trait{
-						{
-							Name: "scaler",
-							Params: map[string]interface{}{
-								"replicas": float64(1),
-							},
-							Template: `
+				},
+				Traits: []*Trait{
+					{
+						Name: "scaler",
+						Params: map[string]interface{}{
+							"replicas": float64(1),
+						},
+						Template: `
 parameter: {
 	// +usage=Specify the number of workload
 	replicas: *1 | int
@@ -390,62 +589,59 @@ parameter: {
 patch: spec: replicas: parameter.replicas
 
 `,
-						},
 					},
 				},
 			},
-			WorkflowSteps: []workflowv1alpha1.WorkflowStep{
-				{
-					WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
-						Name: "apply",
-						Type: "apply-application",
-					},
+		},
+		WorkflowSteps: []workflowv1alpha1.WorkflowStep{
+			{
+				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
+					Name: "apply",
+					Type: "apply-application",
 				},
 			},
-		}
+		},
+	}
 
-		// Create mock client
-		mockClient = test.MockClient{
-			MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
-				if strings.Contains(key.Name, "unknown") {
-					return &errors2.StatusError{ErrStatus: metav1.Status{Reason: "NotFound", Message: "not found"}}
-				}
-				switch o := obj.(type) {
-				case *v1beta1.ComponentDefinition:
-					wd, err := util.UnMarshalStringToComponentDefinition(componentDefinition)
-					if err != nil {
-						return err
-					}
-					*o = *wd
-				case *v1beta1.WorkflowStepDefinition:
-					*o = wsd
-				case *v1beta1.ApplicationRevision:
-					*o = apprev
-				default:
-					// skip
+	// Create mock client
+	mockClient = test.MockClient{
+		MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
+			if strings.Contains(key.Name, "unknown") {
+				return &errors2.StatusError{ErrStatus: metav1.Status{Reason: "NotFound", Message: "not found"}}
+			}
+			switch o := obj.(type) {
+			case *v1beta1.ComponentDefinition:
+				wd, err := util.UnMarshalStringToComponentDefinition(componentDefinition)
+				if err != nil {
+					return err
 				}
-				return nil
-			},
-		}
-	})
+				*o = *wd
+			case *v1beta1.WorkflowStepDefinition:
+				*o = wsd
+			case *v1beta1.ApplicationRevision:
+				*o = apprev
+			default:
+				// skip
+			}
+			return nil
+		},
+	}
 
-	When("with apply-application workflowStep", func() {
-		BeforeEach(func() {
-			// prepare application
-			Expect(common2.ReadYamlToObject("testdata/backport-1-2/app.yaml", &app)).Should(BeNil())
-			// prepare application revision
-			Expect(common2.ReadYamlToObject("testdata/backport-1-2/apprev1.yaml", &apprev)).Should(BeNil())
-		})
+	t.Run("with apply-application workflowStep", func(t *testing.T) {
+		// prepare application
+		assert.NoError(t, common2.ReadYamlToObject("testdata/backport-1-2/app.yaml", &app))
+		// prepare application revision
+		assert.NoError(t, common2.ReadYamlToObject("testdata/backport-1-2/apprev1.yaml", &apprev))
 
-		It("Test we can parse an application revision to an appFile 1", func() {
+		t.Run("Test we can parse an application revision to an appFile 1", func(t *testing.T) {
 
 			appfile, err := NewApplicationParser(&mockClient).GenerateAppFile(context.TODO(), &app)
-			Expect(err).ShouldNot(HaveOccurred())
-			Expect(equal(expectedExceptAppfile, appfile)).Should(BeTrue())
-			Expect(len(appfile.WorkflowSteps) > 0 &&
-				len(appfile.RelatedWorkflowStepDefinitions) == len(appfile.AppRevision.Spec.WorkflowStepDefinitions)).Should(BeTrue())
+			assert.NoError(t, err)
+			assert.True(t, equal(expectedExceptAppfile, appfile))
+			assert.True(t, len(appfile.WorkflowSteps) > 0 &&
+				len(appfile.RelatedWorkflowStepDefinitions) == len(appfile.AppRevision.Spec.WorkflowStepDefinitions))
 
-			Expect(len(appfile.WorkflowSteps) > 0 && func() bool {
+			assert.True(t, len(appfile.WorkflowSteps) > 0 && func() bool {
 				this := appfile.RelatedWorkflowStepDefinitions
 				that := appfile.AppRevision.Spec.WorkflowStepDefinitions
 				for i, w := range this {
@@ -455,27 +651,25 @@ patch: spec: replicas: parameter.replicas
 					}
 				}
 				return true
-			}()).Should(BeTrue())
+			}())
 		})
 	})
 
-	When("with apply-application and apply-component build-in workflowStep", func() {
-		BeforeEach(func() {
-			// prepare application
-			Expect(common2.ReadYamlToObject("testdata/backport-1-2/app.yaml", &app)).Should(BeNil())
-			// prepare application revision
-			Expect(common2.ReadYamlToObject("testdata/backport-1-2/apprev2.yaml", &apprev)).Should(BeNil())
-		})
+	t.Run("with apply-application and apply-component build-in workflowStep", func(t *testing.T) {
+		// prepare application
+		assert.NoError(t, common2.ReadYamlToObject("testdata/backport-1-2/app.yaml", &app))
+		// prepare application revision
+		assert.NoError(t, common2.ReadYamlToObject("testdata/backport-1-2/apprev2.yaml", &apprev))
 
-		It("Test we can parse an application revision to an appFile 2", func() {
+		t.Run("Test we can parse an application revision to an appFile 2", func(t *testing.T) {
 
 			appfile, err := NewApplicationParser(&mockClient).GenerateAppFile(context.TODO(), &app)
-			Expect(err).ShouldNot(HaveOccurred())
-			Expect(equal(expectedExceptAppfile, appfile)).Should(BeTrue())
-			Expect(len(appfile.WorkflowSteps) > 0 &&
-				len(appfile.RelatedWorkflowStepDefinitions) == len(appfile.AppRevision.Spec.WorkflowStepDefinitions)).Should(BeTrue())
+			assert.NoError(t, err)
+			assert.True(t, equal(expectedExceptAppfile, appfile))
+			assert.True(t, len(appfile.WorkflowSteps) > 0 &&
+				len(appfile.RelatedWorkflowStepDefinitions) == len(appfile.AppRevision.Spec.WorkflowStepDefinitions))
 
-			Expect(len(appfile.WorkflowSteps) > 0 && func() bool {
+			assert.True(t, len(appfile.WorkflowSteps) > 0 && func() bool {
 				this := appfile.RelatedWorkflowStepDefinitions
 				that := appfile.AppRevision.Spec.WorkflowStepDefinitions
 				for i, w := range this {
@@ -486,29 +680,25 @@ patch: spec: replicas: parameter.replicas
 					}
 				}
 				return true
-			}()).Should(BeTrue())
+			}())
 		})
 	})
 
-	When("with unknown workflowStep", func() {
-		BeforeEach(func() {
-			// prepare application
-			Expect(common2.ReadYamlToObject("testdata/backport-1-2/app.yaml", &app)).Should(BeNil())
-			// prepare application revision
-			Expect(common2.ReadYamlToObject("testdata/backport-1-2/apprev3.yaml", &apprev)).Should(BeNil())
-		})
+	t.Run("with unknown workflowStep", func(t *testing.T) {
+		// prepare application
+		assert.NoError(t, common2.ReadYamlToObject("testdata/backport-1-2/app.yaml", &app))
+		// prepare application revision
+		assert.NoError(t, common2.ReadYamlToObject("testdata/backport-1-2/apprev3.yaml", &apprev))
 
-		It("Test we can parse an application revision to an appFile 3", func() {
+		t.Run("Test we can parse an application revision to an appFile 3", func(t *testing.T) {
 
 			_, err := NewApplicationParser(&mockClient).GenerateAppFile(context.TODO(), &app)
-			Expect(err).Should(HaveOccurred())
-			Expect(err.Error()).Should(SatisfyAll(
-				ContainSubstring("failed to get workflow step definition apply-application-unknown: not found"),
-				ContainSubstring("failed to parseWorkflowStepsForLegacyRevision")),
-			)
+			assert.Error(t, err)
+			assert.Contains(t, err.Error(), "failed to get workflow step definition apply-application-unknown: not found")
+			assert.Contains(t, err.Error(), "failed to parseWorkflowStepsForLegacyRevision")
 		})
 	})
-})
+}
 
 func TestParser_parseTraits(t *testing.T) {
 	type args struct {
@@ -707,3 +897,145 @@ func TestParser_parseTraitsFromRevision(t *testing.T) {
 		})
 	}
 }
+
+func TestParseComponentFromRevisionAndClient(t *testing.T) {
+	compDef := &v1beta1.ComponentDefinition{
+		ObjectMeta: metav1.ObjectMeta{Name: "webservice", Namespace: "vela-system"},
+		Spec: v1beta1.ComponentDefinitionSpec{
+			Workload:  common.WorkloadTypeDescriptor{Type: "Deployment"},
+			Schematic: &common.Schematic{CUE: &common.CUE{Template: "parameter: {image: string}"}},
+		},
+	}
+	traitDef := &v1beta1.TraitDefinition{
+		ObjectMeta: metav1.ObjectMeta{Name: "scaler", Namespace: "vela-system"},
+		Spec: v1beta1.TraitDefinitionSpec{
+			Schematic: &common.Schematic{CUE: &common.CUE{Template: "parameter: {replicas: int}"}},
+		},
+	}
+
+	appComp := common.ApplicationComponent{
+		Name:       "my-comp",
+		Type:       "webservice",
+		Properties: util.Object2RawExtension(map[string]string{"image": "nginx"}),
+		Traits: []common.ApplicationTrait{
+			{
+				Type:       "scaler",
+				Properties: util.Object2RawExtension(map[string]int{"replicas": 2}),
+			},
+		},
+	}
+
+	schemes := runtime.NewScheme()
+	v1beta1.AddToScheme(schemes)
+
+	tests := []struct {
+		name       string
+		appRev     *v1beta1.ApplicationRevision
+		client     client.Client
+		wantErr    bool
+		assertFunc func(*testing.T, *Component)
+	}{
+		{
+			name: "component and trait found in revision",
+			appRev: &v1beta1.ApplicationRevision{
+				Spec: v1beta1.ApplicationRevisionSpec{
+					ApplicationRevisionCompressibleFields: v1beta1.ApplicationRevisionCompressibleFields{
+						ComponentDefinitions: map[string]*v1beta1.ComponentDefinition{"webservice": compDef},
+						TraitDefinitions:     map[string]*v1beta1.TraitDefinition{"scaler": traitDef},
+					},
+				},
+			},
+			client:  fake.NewClientBuilder().WithScheme(schemes).Build(),
+			wantErr: false,
+			assertFunc: func(t *testing.T, c *Component) {
+				assert.NotNil(t, c)
+				assert.Equal(t, "my-comp", c.Name)
+				assert.Equal(t, "webservice", c.Type)
+				assert.Equal(t, 1, len(c.Traits))
+				assert.Equal(t, "scaler", c.Traits[0].Name)
+			},
+		},
+		{
+			name: "component not in revision, but in cluster",
+			appRev: &v1beta1.ApplicationRevision{
+				Spec: v1beta1.ApplicationRevisionSpec{
+					ApplicationRevisionCompressibleFields: v1beta1.ApplicationRevisionCompressibleFields{
+						TraitDefinitions: map[string]*v1beta1.TraitDefinition{"scaler": traitDef},
+					},
+				},
+			},
+			client:  fake.NewClientBuilder().WithScheme(schemes).WithObjects(compDef).Build(),
+			wantErr: false,
+			assertFunc: func(t *testing.T, c *Component) {
+				assert.NotNil(t, c)
+				assert.Equal(t, "webservice", c.Type)
+				assert.Equal(t, 1, len(c.Traits))
+				assert.Equal(t, "scaler", c.Traits[0].Name)
+			},
+		},
+		{
+			name: "trait not in revision, but in cluster",
+			appRev: &v1beta1.ApplicationRevision{
+				Spec: v1beta1.ApplicationRevisionSpec{
+					ApplicationRevisionCompressibleFields: v1beta1.ApplicationRevisionCompressibleFields{
+						ComponentDefinitions: map[string]*v1beta1.ComponentDefinition{"webservice": compDef},
+					},
+				},
+			},
+			client:  fake.NewClientBuilder().WithScheme(schemes).WithObjects(traitDef).Build(),
+			wantErr: false,
+			assertFunc: func(t *testing.T, c *Component) {
+				assert.NotNil(t, c)
+				assert.Equal(t, "webservice", c.Type)
+				assert.Equal(t, 1, len(c.Traits))
+				assert.Equal(t, "scaler", c.Traits[0].Name)
+			},
+		},
+		{
+			name:    "component and trait not in revision, but in cluster",
+			appRev:  &v1beta1.ApplicationRevision{},
+			client:  fake.NewClientBuilder().WithScheme(schemes).WithObjects(compDef, traitDef).Build(),
+			wantErr: false,
+			assertFunc: func(t *testing.T, c *Component) {
+				assert.NotNil(t, c)
+				assert.Equal(t, "webservice", c.Type)
+				assert.Equal(t, 1, len(c.Traits))
+				assert.Equal(t, "scaler", c.Traits[0].Name)
+			},
+		},
+		{
+			name:    "component not found anywhere",
+			appRev:  &v1beta1.ApplicationRevision{},
+			client:  fake.NewClientBuilder().WithScheme(schemes).Build(),
+			wantErr: true,
+		},
+		{
+			name: "trait not found anywhere",
+			appRev: &v1beta1.ApplicationRevision{
+				Spec: v1beta1.ApplicationRevisionSpec{
+					ApplicationRevisionCompressibleFields: v1beta1.ApplicationRevisionCompressibleFields{
+						ComponentDefinitions: map[string]*v1beta1.ComponentDefinition{"webservice": compDef},
+					},
+				},
+			},
+			client:  fake.NewClientBuilder().WithScheme(schemes).Build(),
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := NewApplicationParser(tt.client)
+			comp, err := p.ParseComponentFromRevisionAndClient(context.Background(), appComp, tt.appRev)
+
+			if tt.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.NoError(t, err)
+				if tt.assertFunc != nil {
+					tt.assertFunc(t, comp)
+				}
+			}
+		})
+	}
+}
diff --git a/pkg/appfile/suit_test.go b/pkg/appfile/suit_test.go
deleted file mode 100644
index 871bc309..00000000
--- a/pkg/appfile/suit_test.go
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
-Copyright 2021 The KubeVela Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package appfile
-
-import (
-	"path/filepath"
-	"testing"
-	"time"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	v1 "k8s.io/api/apps/v1"
-	"k8s.io/apimachinery/pkg/runtime"
-	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
-	"k8s.io/client-go/rest"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/envtest"
-	logf "sigs.k8s.io/controller-runtime/pkg/log"
-	"sigs.k8s.io/controller-runtime/pkg/log/zap"
-
-	coreoam "github.com/oam-dev/kubevela/apis/core.oam.dev"
-	// +kubebuilder:scaffold:imports
-)
-
-var cfg *rest.Config
-var scheme *runtime.Scheme
-var k8sClient client.Client
-var testEnv *envtest.Environment
-
-func TestAppFile(t *testing.T) {
-	RegisterFailHandler(Fail)
-	RunSpecs(t, "Cli Suite")
-}
-
-var _ = BeforeSuite(func() {
-	logf.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)))
-	By("bootstrapping test environment")
-	useExistCluster := false
-	testEnv = &envtest.Environment{
-		ControlPlaneStartTimeout: time.Minute,
-		ControlPlaneStopTimeout:  time.Minute,
-		CRDDirectoryPaths:        []string{filepath.Join("..", "..", "charts", "vela-core", "crds")},
-		UseExistingCluster:       &useExistCluster,
-	}
-
-	var err error
-	cfg, err = testEnv.Start()
-	Expect(err).ToNot(HaveOccurred())
-	Expect(cfg).ToNot(BeNil())
-	scheme = runtime.NewScheme()
-	Expect(coreoam.AddToScheme(scheme)).NotTo(HaveOccurred())
-	Expect(clientgoscheme.AddToScheme(scheme)).NotTo(HaveOccurred())
-	Expect(v1.AddToScheme(scheme)).NotTo(HaveOccurred())
-	k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
-	Expect(err).ToNot(HaveOccurred())
-	Expect(k8sClient).ToNot(BeNil())
-})
-
-var _ = AfterSuite(func() {
-	By("tearing down the test environment")
-	err := testEnv.Stop()
-	Expect(err).ToNot(HaveOccurred())
-})
diff --git a/pkg/appfile/template_test.go b/pkg/appfile/template_test.go
index 0708f7dd..a87026ab 100644
--- a/pkg/appfile/template_test.go
+++ b/pkg/appfile/template_test.go
@@ -18,14 +18,19 @@ package appfile
 
 import (
 	"context"
+	"errors"
+	"fmt"
 	"testing"
 
 	"cuelang.org/go/cue/cuecontext"
 	"github.com/crossplane/crossplane-runtime/pkg/test"
 	"github.com/google/go-cmp/cmp"
 	"github.com/stretchr/testify/assert"
+	"k8s.io/apimachinery/pkg/api/meta"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
 	ktypes "k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
@@ -35,6 +40,24 @@ import (
 	oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
 )
 
+type fakeRESTMapper struct {
+	meta.RESTMapper
+}
+
+func (f fakeRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
+	if resource.Resource == "deployments" {
+		return schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, nil
+	}
+	return schema.GroupVersionKind{}, errors.New("no mapping for KindFor")
+}
+
+func (f fakeRESTMapper) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
+	if resource.Resource == "deployments" {
+		return []schema.GroupVersionKind{{Group: "apps", Version: "v1", Kind: "Deployment"}}, nil
+	}
+	return nil, errors.New("no mapping for KindsFor")
+}
+
 func TestLoadComponentTemplate(t *testing.T) {
 	cueTemplate := `
       context: {
@@ -47,35 +70,35 @@ func TestLoadComponentTemplate(t *testing.T) {
       		selector: matchLabels: {
       			"app.oam.dev/component": context.name
       		}
-      
+
       		template: {
       			metadata: labels: {
       				"app.oam.dev/component": context.name
       			}
-      
+
       			spec: {
       				containers: [{
       					name:  context.name
       					image: parameter.image
-      
+
       					if parameter["cmd"] != _|_ {
       						command: parameter.cmd
       					}
       				}]
       			}
       		}
-      
+
       		selector:
       			matchLabels:
       				"app.oam.dev/component": context.name
       	}
       }
-      
+
       parameter: {
       	// +usage=Which image would you like to use for your service
       	// +short=i
       	image: string
-      
+
       	cmd?: [...string]
       }
       `
@@ -155,7 +178,7 @@ func TestLoadTraitTemplate(t *testing.T) {
         }
 
         outputs: ingress: {
-        	apiVersion: "networking.k8s.io/v1beta1"
+        	apiVersion: "networking.k8s.io/v1"
         	kind:       "Ingress"
         	metadata:
         		name: context.name
@@ -166,9 +189,14 @@ func TestLoadTraitTemplate(t *testing.T) {
         				paths: [
         					for k, v in parameter.http {
         						path: k
+        						pathType: "Prefix"
         						backend: {
-        							serviceName: context.name
-        							servicePort: v
+        							service: {
+        								name: context.name
+        								port: {
+        									number: v
+        								}
+        							}
         						}
         					},
         				]
@@ -311,7 +339,7 @@ metadata:
 spec:
   status:
     customStatus: testCustomStatus
-    healthPolicy: testHealthPolicy 
+    healthPolicy: testHealthPolicy
   workload:
     definition:
       apiVersion: apps/v1
@@ -328,7 +356,7 @@ metadata:
 spec:
   status:
     customStatus: testCustomStatus
-    healthPolicy: testHealthPolicy 
+    healthPolicy: testHealthPolicy
   appliesToWorkloads:
     - deployments.apps
   schematic:
@@ -380,3 +408,349 @@ spec:
 		t.Fatal("failed load template of trait definition ", diff)
 	}
 }
+
+func TestLoadTemplateFromRevision(t *testing.T) {
+	compDef := v1beta1.ComponentDefinition{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       v1beta1.ComponentDefinitionKind,
+			APIVersion: v1beta1.SchemeGroupVersion.String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "my-comp"},
+		Spec: v1beta1.ComponentDefinitionSpec{
+			Schematic: &common.Schematic{
+				CUE: &common.CUE{Template: "parameter: {name: string}"},
+			},
+			Workload: common.WorkloadTypeDescriptor{
+				Definition: common.WorkloadGVK{APIVersion: "v1", Kind: "Pod"},
+			},
+		},
+	}
+	traitDef := v1beta1.TraitDefinition{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       v1beta1.TraitDefinitionKind,
+			APIVersion: v1beta1.SchemeGroupVersion.String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "my-trait"},
+		Spec: v1beta1.TraitDefinitionSpec{
+			Schematic: &common.Schematic{
+				CUE: &common.CUE{Template: "parameter: {port: int}"},
+			},
+		},
+	}
+	policyDef := v1beta1.PolicyDefinition{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       v1beta1.PolicyDefinitionKind,
+			APIVersion: v1beta1.SchemeGroupVersion.String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "my-policy"},
+		Spec: v1beta1.PolicyDefinitionSpec{
+			Schematic: &common.Schematic{
+				CUE: &common.CUE{Template: "parameter: {replicas: int}"},
+			},
+		},
+	}
+	wfStepDef := v1beta1.WorkflowStepDefinition{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       v1beta1.WorkflowStepDefinitionKind,
+			APIVersion: v1beta1.SchemeGroupVersion.String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "my-step"},
+		Spec: v1beta1.WorkflowStepDefinitionSpec{
+			Schematic: &common.Schematic{
+				CUE: &common.CUE{Template: "parameter: {image: string}"},
+			},
+		},
+	}
+	wlDef := v1beta1.WorkloadDefinition{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       v1beta1.WorkloadDefinitionKind,
+			APIVersion: v1beta1.SchemeGroupVersion.String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "my-workload"},
+		Spec: v1beta1.WorkloadDefinitionSpec{
+			Reference: common.DefinitionReference{
+				Name: "deployments.apps",
+			},
+			Schematic: &common.Schematic{
+				CUE: &common.CUE{Template: "output: {apiVersion: 'apps/v1', kind: 'Deployment'}"},
+			},
+		},
+	}
+
+	appRev := &v1beta1.ApplicationRevision{
+		ObjectMeta: metav1.ObjectMeta{Name: "my-app-rev"},
+		Spec: v1beta1.ApplicationRevisionSpec{
+			ApplicationRevisionCompressibleFields: v1beta1.ApplicationRevisionCompressibleFields{
+				ComponentDefinitions: map[string]*v1beta1.ComponentDefinition{
+					"my-comp": &compDef,
+				},
+				TraitDefinitions: map[string]*v1beta1.TraitDefinition{
+					"my-trait": &traitDef,
+				},
+				PolicyDefinitions: map[string]v1beta1.PolicyDefinition{
+					"my-policy": policyDef,
+				},
+				WorkflowStepDefinitions: map[string]*v1beta1.WorkflowStepDefinition{
+					"my-step": &wfStepDef,
+				},
+				WorkloadDefinitions: map[string]v1beta1.WorkloadDefinition{
+					"my-workload": wlDef,
+				},
+			},
+		},
+	}
+
+	mapper := fakeRESTMapper{}
+
+	testCases := map[string]struct {
+		capName   string
+		capType   types.CapType
+		apprev    *v1beta1.ApplicationRevision
+		checkFunc func(t *testing.T, tmpl *Template, err error)
+	}{
+		"load component definition": {
+			capName: "my-comp",
+			capType: types.TypeComponentDefinition,
+			apprev:  appRev,
+			checkFunc: func(t *testing.T, tmpl *Template, err error) {
+				assert.NoError(t, err)
+				assert.NotNil(t, tmpl)
+				assert.Equal(t, "parameter: {name: string}", tmpl.TemplateStr)
+				assert.Equal(t, v1beta1.ComponentDefinitionKind, tmpl.ComponentDefinition.Kind)
+			},
+		},
+		"load trait definition": {
+			capName: "my-trait",
+			capType: types.TypeTrait,
+			apprev:  appRev,
+			checkFunc: func(t *testing.T, tmpl *Template, err error) {
+				assert.NoError(t, err)
+				assert.NotNil(t, tmpl)
+				assert.Equal(t, "parameter: {port: int}", tmpl.TemplateStr)
+				assert.Equal(t, v1beta1.TraitDefinitionKind, tmpl.TraitDefinition.Kind)
+			},
+		},
+		"load policy definition": {
+			capName: "my-policy",
+			capType: types.TypePolicy,
+			apprev:  appRev,
+			checkFunc: func(t *testing.T, tmpl *Template, err error) {
+				assert.NoError(t, err)
+				assert.NotNil(t, tmpl)
+				assert.Equal(t, "parameter: {replicas: int}", tmpl.TemplateStr)
+				assert.Equal(t, v1beta1.PolicyDefinitionKind, tmpl.PolicyDefinition.Kind)
+			},
+		},
+		"load workflow step definition": {
+			capName: "my-step",
+			capType: types.TypeWorkflowStep,
+			apprev:  appRev,
+			checkFunc: func(t *testing.T, tmpl *Template, err error) {
+				assert.NoError(t, err)
+				assert.NotNil(t, tmpl)
+				assert.Equal(t, "parameter: {image: string}", tmpl.TemplateStr)
+				assert.Equal(t, v1beta1.WorkflowStepDefinitionKind, tmpl.WorkflowStepDefinition.Kind)
+			},
+		},
+		"fallback to workload definition": {
+			capName: "my-workload",
+			capType: types.TypeComponentDefinition,
+			apprev:  appRev,
+			checkFunc: func(t *testing.T, tmpl *Template, err error) {
+				assert.NoError(t, err)
+				assert.NotNil(t, tmpl)
+				assert.Equal(t, "output: {apiVersion: 'apps/v1', kind: 'Deployment'}", tmpl.TemplateStr)
+				assert.NotNil(t, tmpl.WorkloadDefinition)
+				assert.Equal(t, v1beta1.WorkloadDefinitionKind, tmpl.WorkloadDefinition.Kind)
+				assert.Equal(t, "apps/v1", tmpl.Reference.Definition.APIVersion)
+				assert.Equal(t, "Deployment", tmpl.Reference.Definition.Kind)
+			},
+		},
+		"definition not found": {
+			capName: "not-exist",
+			capType: types.TypeComponentDefinition,
+			apprev:  appRev,
+			checkFunc: func(t *testing.T, tmpl *Template, err error) {
+				assert.Error(t, err)
+				assert.Nil(t, tmpl)
+				assert.True(t, IsNotFoundInAppRevision(err))
+				assert.Contains(t, err.Error(), "component definition [not-exist] not found in app revision my-app-rev")
+			},
+		},
+		"nil app revision": {
+			capName: "any",
+			capType: types.TypeComponentDefinition,
+			apprev:  nil,
+			checkFunc: func(t *testing.T, tmpl *Template, err error) {
+				assert.Error(t, err)
+				assert.Nil(t, tmpl)
+				assert.Contains(t, err.Error(), "fail to find template for any as app revision is empty")
+			},
+		},
+		"unsupported type": {
+			capName: "any",
+			capType: "unsupported",
+			apprev:  appRev,
+			checkFunc: func(t *testing.T, tmpl *Template, err error) {
+				assert.Error(t, err)
+				assert.Nil(t, tmpl)
+				assert.Contains(t, err.Error(), "kind(unsupported) of any not supported")
+			},
+		},
+		"verify revision name": {
+			capName: "my-comp@my-ns",
+			capType: types.TypeComponentDefinition,
+			apprev:  appRev,
+			checkFunc: func(t *testing.T, tmpl *Template, err error) {
+				assert.NoError(t, err)
+				assert.NotNil(t, tmpl)
+				assert.Equal(t, "parameter: {name: string}", tmpl.TemplateStr)
+			},
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			tmpl, err := LoadTemplateFromRevision(tc.capName, tc.capType, tc.apprev, mapper)
+			tc.checkFunc(t, tmpl, err)
+		})
+	}
+}
+
+func TestConvertTemplateJSON2Object(t *testing.T) {
+	testCases := map[string]struct {
+		capName   string
+		in        *runtime.RawExtension
+		schematic *common.Schematic
+		wantCap   types.Capability
+		wantErr   bool
+	}{
+		"with schematic CUE": {
+			capName: "test-cap",
+			schematic: &common.Schematic{
+				CUE: &common.CUE{Template: "parameter: {name: string}"},
+			},
+			wantCap: types.Capability{
+				Name:        "test-cap",
+				CueTemplate: "parameter: {name: string}",
+			},
+			wantErr: false,
+		},
+		"with RawExtension": {
+			capName: "test-cap-2",
+			in: &runtime.RawExtension{
+				Raw: []byte(`{"template": "parameter: {age: int}"}`),
+			},
+			wantCap: types.Capability{
+				Name:        "test-cap-2",
+				CueTemplate: "parameter: {age: int}",
+			},
+			wantErr: false,
+		},
+		"with both schematic and RawExtension": {
+			capName: "test-cap-3",
+			in: &runtime.RawExtension{
+				Raw: []byte(`{"description": "test"}`),
+			},
+			schematic: &common.Schematic{
+				CUE: &common.CUE{Template: "parameter: {name: string}"},
+			},
+			wantCap: types.Capability{
+				Name:        "test-cap-3",
+				Description: "test",
+				CueTemplate: "parameter: {name: string}",
+			},
+			wantErr: false,
+		},
+		"with invalid JSON in RawExtension": {
+			capName: "test-cap-4",
+			in: &runtime.RawExtension{
+				Raw: []byte(`{"template": "parameter: {age: int}"`),
+			},
+			wantErr: true,
+		},
+		"with no template": {
+			capName: "test-cap-5",
+			in:      &runtime.RawExtension{Raw: []byte(`{"description": "test"}`)},
+			wantCap: types.Capability{
+				Name:        "test-cap-5",
+				Description: "test",
+			},
+			wantErr: false,
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			cap, err := ConvertTemplateJSON2Object(tc.capName, tc.in, tc.schematic)
+			if tc.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.NoError(t, err)
+				if diff := cmp.Diff(tc.wantCap, cap); diff != "" {
+					t.Errorf("ConvertTemplateJSON2Object() (-want, +got):\n%s", diff)
+				}
+			}
+		})
+	}
+}
+
+func TestTemplateAsStatusRequest(t *testing.T) {
+	tmpl := &Template{
+		Health:       "isHealth: true",
+		CustomStatus: "message: 'Ready'",
+		Details:      "details: 'some details'",
+	}
+	params := map[string]interface{}{
+		"param1": "value1",
+	}
+	statusReq := tmpl.AsStatusRequest(params)
+
+	assert.Equal(t, "isHealth: true", statusReq.Health)
+	assert.Equal(t, "message: 'Ready'", statusReq.Custom)
+	assert.Equal(t, "details: 'some details'", statusReq.Details)
+	assert.Equal(t, params, statusReq.Parameter)
+}
+
+func TestIsNotFoundInAppRevision(t *testing.T) {
+	testCases := map[string]struct {
+		err      error
+		expected bool
+	}{
+		"component definition not found": {
+			err:      fmt.Errorf("component definition [my-comp] not found in app revision [my-app-rev]"),
+			expected: true,
+		},
+		"trait definition not found": {
+			err:      fmt.Errorf("trait definition [my-trait] not found in app revision [my-app-rev]"),
+			expected: true,
+		},
+		"policy definition not found": {
+			err:      fmt.Errorf("policy definition [my-policy] not found in app revision [my-app-rev]"),
+			expected: true,
+		},
+		"workflow step definition not found": {
+			err:      fmt.Errorf("workflow step definition [my-step] not found in app revision [my-app-rev]"),
+			expected: true,
+		},
+		"different error": {
+			err:      errors.New("a completely different error"),
+			expected: false,
+		},
+		"nil error": {
+			err:      nil,
+			expected: false,
+		},
+		"error with similar text but not exactly": {
+			err:      fmt.Errorf("this resource is not found in revision of app"),
+			expected: false,
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			result := IsNotFoundInAppRevision(tc.err)
+			assert.Equal(t, tc.expected, result)
+		})
+	}
+}
diff --git a/pkg/appfile/unit_test.go b/pkg/appfile/unit_test.go
deleted file mode 100644
index 297559c0..00000000
--- a/pkg/appfile/unit_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
-Copyright 2023 The KubeVela Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-	http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package appfile
-
-import (
-	"context"
-	"fmt"
-	"testing"
-
-	"github.com/stretchr/testify/require"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/runtime"
-	"sigs.k8s.io/controller-runtime/pkg/client/fake"
-
-	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
-	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
-)
-
-func TestIsNotFoundInAppFile(t *testing.T) {
-	require.True(t, IsNotFoundInAppFile(fmt.Errorf("ComponentDefinition XXX not found in appfile")))
-}
-
-func TestIsNotFoundInAppRevision(t *testing.T) {
-	require.True(t, IsNotFoundInAppRevision(fmt.Errorf("ComponentDefinition XXX not found in app revision")))
-}
-
-func TestParseComponentFromRevisionAndClient(t *testing.T) {
-	ctx := context.Background()
-	cli := fake.NewClientBuilder().WithScheme(scheme).Build()
-	p := &Parser{
-		client:     cli,
-		tmplLoader: LoadTemplate,
-	}
-	comp := common.ApplicationComponent{
-		Name:       "test",
-		Type:       "test",
-		Properties: &runtime.RawExtension{Raw: []byte(`{}`)},
-		Traits: []common.ApplicationTrait{{
-			Type:       "tr",
-			Properties: &runtime.RawExtension{Raw: []byte(`{}`)},
-		}, {
-			Type:       "internal",
-			Properties: &runtime.RawExtension{Raw: []byte(`{}`)},
-		}},
-	}
-	appRev := &v1beta1.ApplicationRevision{}
-	cd := &v1beta1.ComponentDefinition{ObjectMeta: metav1.ObjectMeta{Name: "test"}}
-	td := &v1beta1.TraitDefinition{ObjectMeta: metav1.ObjectMeta{Name: "tr"}}
-	require.NoError(t, cli.Create(ctx, cd))
-	require.NoError(t, cli.Create(ctx, td))
-	appRev.Spec.TraitDefinitions = map[string]*v1beta1.TraitDefinition{"internal": {}}
-	_, err := p.ParseComponentFromRevisionAndClient(ctx, comp, appRev)
-	require.NoError(t, err)
-
-	_comp1 := comp.DeepCopy()
-	_comp1.Type = "bad"
-	_, err = p.ParseComponentFromRevisionAndClient(ctx, *_comp1, appRev)
-	require.Error(t, err)
-
-	_comp2 := comp.DeepCopy()
-	_comp2.Traits[0].Type = "bad"
-	_, err = p.ParseComponentFromRevisionAndClient(ctx, *_comp2, appRev)
-	require.Error(t, err)
-
-	_comp3 := comp.DeepCopy()
-	_comp3.Traits[0].Properties = &runtime.RawExtension{Raw: []byte(`bad`)}
-	_, err = p.ParseComponentFromRevisionAndClient(ctx, *_comp3, appRev)
-	require.Error(t, err)
-}
diff --git a/pkg/appfile/validate_test.go b/pkg/appfile/validate_test.go
index 4fe61bc7..7720a799 100644
--- a/pkg/appfile/validate_test.go
+++ b/pkg/appfile/validate_test.go
@@ -17,67 +17,32 @@ limitations under the License.
 package appfile
 
 import (
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
+	"testing"
+
+	"cuelang.org/go/cue"
+	"github.com/stretchr/testify/assert"
+	utilfeature "k8s.io/apiserver/pkg/util/feature"
 
 	"github.com/oam-dev/kubevela/apis/types"
 	"github.com/oam-dev/kubevela/pkg/cue/definition"
+	"github.com/oam-dev/kubevela/pkg/features"
 )
 
-var _ = Describe("Test validate CUE schematic Appfile", func() {
+func TestTrait_EvalContext_OutputNameUniqueness(t *testing.T) {
 	type SubTestCase struct {
+		name          string
 		compDefTmpl   string
 		traitDefTmpl1 string
 		traitDefTmpl2 string
 		wantErrMsg    string
 	}
 
-	DescribeTable("Test validate outputs name unique", func(tc SubTestCase) {
-		Expect("").Should(BeEmpty())
-		wl := &Component{
-			Name:               "myweb",
-			Type:               "worker",
-			CapabilityCategory: types.CUECategory,
-			Traits: []*Trait{
-				{
-					Name:               "myscaler",
-					CapabilityCategory: types.CUECategory,
-					Template:           tc.traitDefTmpl1,
-					engine:             definition.NewTraitAbstractEngine("myscaler"),
-				},
-				{
-					Name:               "myingress",
-					CapabilityCategory: types.CUECategory,
-					Template:           tc.traitDefTmpl2,
-					engine:             definition.NewTraitAbstractEngine("myingress"),
-				},
-			},
-			FullTemplate: &Template{
-				TemplateStr: tc.compDefTmpl,
-			},
-			engine: definition.NewWorkloadAbstractEngine("myweb"),
-		}
-
-		ctxData := GenerateContextDataFromAppFile(&Appfile{
-			Name:            "myapp",
-			Namespace:       "test-ns",
-			AppRevisionName: "myapp-v1",
-		}, wl.Name)
-		pCtx, err := newValidationProcessContext(wl, ctxData)
-		Expect(err).Should(BeNil())
-		Eventually(func() string {
-			for _, tr := range wl.Traits {
-				if err := tr.EvalContext(pCtx); err != nil {
-					return err.Error()
-				}
-			}
-			return ""
-		}).Should(ContainSubstring(tc.wantErrMsg))
-	},
-		Entry("Succeed", SubTestCase{
+	testCases := []SubTestCase{
+		{
+			name: "Succeed",
 			compDefTmpl: `
 			output: {
-				apiVersion: "apps/v1" 
+				apiVersion: "apps/v1"
 				kind: "Deployment"
 				}
 			outputs: mysvc: {
@@ -98,11 +63,12 @@ var _ = Describe("Test validate CUE schematic Appfile", func() {
 			}
 			`,
 			wantErrMsg: "",
-		}),
-		Entry("CompDef and TraitDef have same outputs", SubTestCase{
+		},
+		{
+			name: "CompDef and TraitDef have same outputs",
 			compDefTmpl: `
 			output: {
-				apiVersion: "apps/v1" 
+				apiVersion: "apps/v1"
 				kind: "Deployment"
 				}
 			outputs: mysvc1: {
@@ -123,11 +89,12 @@ var _ = Describe("Test validate CUE schematic Appfile", func() {
 			}
 			`,
 			wantErrMsg: `auxiliary "mysvc1" already exits`,
-		}),
-		Entry("TraitDefs have same outputs", SubTestCase{
+		},
+		{
+			name: "TraitDefs have same outputs",
 			compDefTmpl: `
 			output: {
-				apiVersion: "apps/v1" 
+				apiVersion: "apps/v1"
 				kind: "Deployment"
 				}
 			outputs: mysvc: {
@@ -148,41 +115,72 @@ var _ = Describe("Test validate CUE schematic Appfile", func() {
 			}
 			`,
 			wantErrMsg: `auxiliary "mysvc1" already exits`,
-		}),
-	)
-})
+		},
+	}
 
-var _ = Describe("Test ValidateComponentParams", func() {
-	type ParamTestCase struct {
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			wl := &Component{
+				Name:               "myweb",
+				Type:               "worker",
+				CapabilityCategory: types.CUECategory,
+				Traits: []*Trait{
+					{
+						Name:               "myscaler",
+						CapabilityCategory: types.CUECategory,
+						Template:           tc.traitDefTmpl1,
+						engine:             definition.NewTraitAbstractEngine("myscaler"),
+					},
+					{
+						Name:               "myingress",
+						CapabilityCategory: types.CUECategory,
+						Template:           tc.traitDefTmpl2,
+						engine:             definition.NewTraitAbstractEngine("myingress"),
+					},
+				},
+				FullTemplate: &Template{
+					TemplateStr: tc.compDefTmpl,
+				},
+				engine: definition.NewWorkloadAbstractEngine("myweb"),
+			}
+
+			ctxData := GenerateContextDataFromAppFile(&Appfile{
+				Name:            "myapp",
+				Namespace:       "test-ns",
+				AppRevisionName: "myapp-v1",
+			}, wl.Name)
+			pCtx, err := newValidationProcessContext(wl, ctxData)
+			assert.NoError(t, err)
+
+			var evalErr error
+			for _, tr := range wl.Traits {
+				if err := tr.EvalContext(pCtx); err != nil {
+					evalErr = err
+					break
+				}
+			}
+
+			if tc.wantErrMsg != "" {
+				assert.Error(t, evalErr)
+				assert.Contains(t, evalErr.Error(), tc.wantErrMsg)
+			} else {
+				assert.NoError(t, evalErr)
+			}
+		})
+	}
+}
+
+func TestParser_ValidateComponentParams(t *testing.T) {
+	testCases := []struct {
 		name     string
+		compName string
 		template string
 		params   map[string]interface{}
 		wantErr  string
-	}
-
-	DescribeTable("ValidateComponentParams cases", func(tc ParamTestCase) {
-		wl := &Component{
-			Name:         tc.name,
-			Type:         "worker",
-			FullTemplate: &Template{TemplateStr: tc.template},
-			Params:       tc.params,
-		}
-		app := &Appfile{
-			Name:      "myapp",
-			Namespace: "test-ns",
-		}
-		ctxData := GenerateContextDataFromAppFile(app, wl.Name)
-		parser := &Parser{}
-		err := parser.ValidateComponentParams(ctxData, wl, app)
-		if tc.wantErr == "" {
-			Expect(err).To(BeNil())
-		} else {
-			Expect(err).To(HaveOccurred())
-			Expect(err.Error()).To(ContainSubstring(tc.wantErr))
-		}
-	},
-		Entry("valid params and template", ParamTestCase{
-			name: "valid",
+	}{
+		{
+			name:     "valid params and template",
+			compName: "valid",
 			template: `
 			parameter: {
 				replicas: int | *1
@@ -196,9 +194,10 @@ var _ = Describe("Test ValidateComponentParams", func() {
 				"replicas": 2,
 			},
 			wantErr: "",
-		}),
-		Entry("invalid CUE in template", ParamTestCase{
-			name: "invalid-cue",
+		},
+		{
+			name:     "invalid CUE in template",
+			compName: "invalid-cue",
 			template: `
 			parameter: {
 				replicas: int | *1
@@ -213,9 +212,10 @@ var _ = Describe("Test ValidateComponentParams", func() {
 				"replicas": 2,
 			},
 			wantErr: "CUE compile error",
-		}),
-		Entry("missing required parameter", ParamTestCase{
-			name: "missing-required",
+		},
+		{
+			name:     "missing required parameter",
+			compName: "missing-required",
 			template: `
 			parameter: {
 				replicas: int
@@ -227,9 +227,10 @@ var _ = Describe("Test ValidateComponentParams", func() {
 			`,
 			params:  map[string]interface{}{},
 			wantErr: "component \"missing-required\": missing parameters: replicas",
-		}),
-		Entry("parameter constraint violation", ParamTestCase{
-			name: "constraint-violation",
+		},
+		{
+			name:     "parameter constraint violation",
+			compName: "constraint-violation",
 			template: `
 			parameter: {
 				replicas: int & >0
@@ -243,9 +244,10 @@ var _ = Describe("Test ValidateComponentParams", func() {
 				"replicas": -1,
 			},
 			wantErr: "parameter constraint violation",
-		}),
-		Entry("invalid parameter block", ParamTestCase{
-			name: "invalid-param-block",
+		},
+		{
+			name:     "invalid parameter block",
+			compName: "invalid-param-block",
 			template: `
 			parameter: {
 				replicas: int | *1
@@ -259,6 +261,340 @@ var _ = Describe("Test ValidateComponentParams", func() {
 				"replicas": "not-an-int",
 			},
 			wantErr: "parameter constraint violation",
-		}),
-	)
-})
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			wl := &Component{
+				Name:         tc.compName,
+				Type:         "worker",
+				FullTemplate: &Template{TemplateStr: tc.template},
+				Params:       tc.params,
+			}
+			app := &Appfile{
+				Name:      "myapp",
+				Namespace: "test-ns",
+			}
+			ctxData := GenerateContextDataFromAppFile(app, wl.Name)
+			parser := &Parser{}
+			err := parser.ValidateComponentParams(ctxData, wl, app)
+			if tc.wantErr == "" {
+				assert.NoError(t, err)
+			} else {
+				assert.Error(t, err)
+				assert.Contains(t, err.Error(), tc.wantErr)
+			}
+		})
+	}
+}
+
+func TestValidationHelpers(t *testing.T) {
+	t.Run("renderTemplate", func(t *testing.T) {
+		tmpl := "output: {}"
+		expected := "output: {}\ncontext: _\nparameter: _\n"
+		assert.Equal(t, expected, renderTemplate(tmpl))
+	})
+
+	t.Run("cueParamBlock", func(t *testing.T) {
+		t.Run("should handle empty params", func(t *testing.T) {
+			out, err := cueParamBlock(map[string]any{})
+			assert.NoError(t, err)
+			assert.Equal(t, "parameter: {}", out)
+		})
+
+		t.Run("should handle valid params", func(t *testing.T) {
+			params := map[string]any{"key": "value"}
+			out, err := cueParamBlock(params)
+			assert.NoError(t, err)
+			assert.Equal(t, `parameter: {"key":"value"}`, out)
+		})
+
+		t.Run("should return error for unmarshallable params", func(t *testing.T) {
+			params := map[string]any{"key": make(chan int)}
+			_, err := cueParamBlock(params)
+			assert.Error(t, err)
+		})
+	})
+
+	t.Run("filterMissing", func(t *testing.T) {
+		t.Run("should filter missing keys", func(t *testing.T) {
+			keys := []string{"a", "b.c", "d"}
+			provided := map[string]any{
+				"a": 1,
+				"b": map[string]any{
+					"c": 2,
+				},
+			}
+			out, err := filterMissing(keys, provided)
+			assert.NoError(t, err)
+			assert.Equal(t, []string{"d"}, out)
+		})
+
+		t.Run("should handle no missing keys", func(t *testing.T) {
+			keys := []string{"a"}
+			provided := map[string]any{"a": 1}
+			out, err := filterMissing(keys, provided)
+			assert.NoError(t, err)
+			assert.Empty(t, out)
+		})
+	})
+
+	t.Run("requiredFields", func(t *testing.T) {
+		t.Run("should identify required fields", func(t *testing.T) {
+			cueStr := `
+			parameter: {
+				name: string
+				age: int
+				nested: {
+					field1: string
+					field2: bool
+				}
+			}
+			`
+			var r cue.Runtime
+			inst, err := r.Compile("", cueStr)
+			assert.NoError(t, err)
+			val := inst.Value()
+			paramVal := val.LookupPath(cue.ParsePath("parameter"))
+
+			fields, err := requiredFields(paramVal)
+			assert.NoError(t, err)
+			assert.ElementsMatch(t, []string{"name", "age", "nested.field1", "nested.field2"}, fields)
+		})
+
+		t.Run("should ignore optional and default fields", func(t *testing.T) {
+			cueStr := `
+			parameter: {
+				name: string
+				age?: int
+				location: string | *"unknown"
+				nested: {
+					field1: string
+					field2?: bool
+				}
+			}
+			`
+			var r cue.Runtime
+			inst, err := r.Compile("", cueStr)
+			assert.NoError(t, err)
+			val := inst.Value()
+			paramVal := val.LookupPath(cue.ParsePath("parameter"))
+
+			fields, err := requiredFields(paramVal)
+			assert.NoError(t, err)
+			assert.ElementsMatch(t, []string{"name", "nested.field1"}, fields)
+		})
+	})
+}
+
+func TestEnforceRequiredParams(t *testing.T) {
+	var r cue.Runtime
+	cueStr := `
+		parameter: {
+			image: string
+			replicas: int
+			port: int
+			data: {
+				key: string
+				value: string
+			}
+		}
+		`
+	inst, err := r.Compile("", cueStr)
+	assert.NoError(t, err)
+	root := inst.Value()
+
+	t.Run("should pass if all params are provided directly", func(t *testing.T) {
+		params := map[string]any{
+			"image":    "nginx",
+			"replicas": 2,
+			"port":     80,
+			"data": map[string]any{
+				"key":   "k",
+				"value": "v",
+			},
+		}
+		app := &Appfile{}
+		err := enforceRequiredParams(root, params, app)
+		assert.NoError(t, err)
+	})
+
+	t.Run("should fail if params are missing", func(t *testing.T) {
+		params := map[string]any{
+			"image": "nginx",
+		}
+		app := &Appfile{}
+		err := enforceRequiredParams(root, params, app)
+		assert.Error(t, err)
+		assert.Contains(t, err.Error(), "missing parameters: replicas,port,data.key,data.value")
+	})
+}
+
+func TestParser_ValidateCUESchematicAppfile(t *testing.T) {
+	assert.NoError(t, utilfeature.DefaultMutableFeatureGate.Set(string(features.EnableCueValidation)+"=true"))
+	t.Cleanup(func() {
+		assert.NoError(t, utilfeature.DefaultMutableFeatureGate.Set(string(features.EnableCueValidation)+"=false"))
+	})
+
+	t.Run("should validate a valid CUE schematic appfile", func(t *testing.T) {
+		appfile := &Appfile{
+			Name:      "test-app",
+			Namespace: "test-ns",
+			ParsedComponents: []*Component{
+				{
+					Name:               "my-comp",
+					Type:               "worker",
+					CapabilityCategory: types.CUECategory,
+					Params: map[string]any{
+						"image": "nginx",
+					},
+					FullTemplate: &Template{
+						TemplateStr: `
+							parameter: {
+								image: string
+							}
+							output: {
+								apiVersion: "apps/v1"
+								kind: "Deployment"
+								spec: {
+									template: {
+										spec: {
+											containers: [{
+												name: "my-container"
+												image: parameter.image
+											}]
+										}
+									}
+								}
+							}
+						`,
+					},
+					engine: definition.NewWorkloadAbstractEngine("my-comp"),
+					Traits: []*Trait{
+						{
+							Name:               "my-trait",
+							CapabilityCategory: types.CUECategory,
+							Template: `
+								parameter: {
+								domain: string
+							}
+							patch: {}
+						`,
+							Params: map[string]any{
+								"domain": "example.com",
+							},
+							engine: definition.NewTraitAbstractEngine("my-trait"),
+						},
+					},
+				},
+			},
+		}
+
+		p := &Parser{}
+		err := p.ValidateCUESchematicAppfile(appfile)
+		assert.NoError(t, err)
+	})
+
+	t.Run("should return error for invalid trait evaluation", func(t *testing.T) {
+		appfile := &Appfile{
+			Name:      "test-app",
+			Namespace: "test-ns",
+			ParsedComponents: []*Component{
+				{
+					Name:               "my-comp",
+					Type:               "worker",
+					CapabilityCategory: types.CUECategory,
+					Params: map[string]any{
+						"image": "nginx",
+					},
+					FullTemplate: &Template{
+						TemplateStr: `
+							parameter: {
+								image: string
+							}
+							output: {
+								apiVersion: "apps/v1"
+								kind: "Deployment"
+							}
+						`,
+					},
+					engine: definition.NewWorkloadAbstractEngine("my-comp"),
+					Traits: []*Trait{
+						{
+							Name:               "my-trait",
+							CapabilityCategory: types.CUECategory,
+							Template: `
+								// invalid CUE template
+								parameter: {
+									domain: string
+								}
+							patch: {
+									invalid: {
+							}
+						`,
+							Params: map[string]any{
+								"domain": "example.com",
+							},
+							engine: definition.NewTraitAbstractEngine("my-trait"),
+						},
+					},
+				},
+			},
+		}
+
+		p := &Parser{}
+		err := p.ValidateCUESchematicAppfile(appfile)
+		assert.Error(t, err)
+		assert.Contains(t, err.Error(), "cannot evaluate trait \"my-trait\"")
+	})
+
+	t.Run("should return error for missing parameters", func(t *testing.T) {
+		appfile := &Appfile{
+			Name:      "test-app",
+			Namespace: "test-ns",
+			ParsedComponents: []*Component{
+				{
+					Name:               "my-comp",
+					Type:               "worker",
+					CapabilityCategory: types.CUECategory,
+					Params:             map[string]any{}, // no params provided
+					FullTemplate: &Template{
+						TemplateStr: `
+							parameter: {
+								image: string
+							}
+							output: {
+								apiVersion: "apps/v1"
+								kind: "Deployment"
+							}
+						`,
+					},
+					engine: definition.NewWorkloadAbstractEngine("my-comp"),
+				},
+			},
+		}
+
+		p := &Parser{}
+		err := p.ValidateCUESchematicAppfile(appfile)
+		assert.Error(t, err)
+		assert.Contains(t, err.Error(), "missing parameters: image")
+	})
+
+	t.Run("should skip non-CUE components", func(t *testing.T) {
+		appfile := &Appfile{
+			Name:      "test-app",
+			Namespace: "test-ns",
+			ParsedComponents: []*Component{
+				{
+					Name:               "my-comp",
+					Type:               "helm",
+					CapabilityCategory: types.TerraformCategory,
+				},
+			},
+		}
+		p := &Parser{}
+		err := p.ValidateCUESchematicAppfile(appfile)
+		assert.NoError(t, err)
+	})
+}
diff --git a/pkg/component/main_test.go b/pkg/component/main_test.go
new file mode 100644
index 00000000..423a9769
--- /dev/null
+++ b/pkg/component/main_test.go
@@ -0,0 +1,66 @@
+/*
+Copyright 2021 The KubeVela Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package component
+
+import (
+	"log"
+	"math/rand"
+	"os"
+	"testing"
+	"time"
+
+	"k8s.io/client-go/rest"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/envtest"
+
+	pkgcommon "github.com/oam-dev/kubevela/pkg/utils/common"
+)
+
+var cfg *rest.Config
+var k8sClient client.Client
+var testEnv *envtest.Environment
+
+func TestMain(m *testing.M) {
+	rand.Seed(time.Now().UnixNano())
+
+	testEnv = &envtest.Environment{
+		ControlPlaneStartTimeout: time.Minute * 3,
+		ControlPlaneStopTimeout:  time.Minute,
+		UseExistingCluster:       ptr.To(false),
+		CRDDirectoryPaths:        []string{"./testdata"},
+	}
+
+	var err error
+	cfg, err = testEnv.Start()
+	if err != nil {
+		log.Fatalf("Failed to start test environment: %v", err)
+	}
+
+	cfg.Timeout = time.Minute * 2
+	k8sClient, err = client.New(cfg, client.Options{Scheme: pkgcommon.Scheme})
+	if err != nil {
+		log.Fatalf("Failed to create new kube client: %v", err)
+	}
+
+	code := m.Run()
+
+	if err = testEnv.Stop(); err != nil {
+		log.Printf("Failed to tear down the test environment: %v", err)
+	}
+	os.Exit(code)
+}
diff --git a/pkg/component/ref_objects_suite_test.go b/pkg/component/ref_objects_suite_test.go
deleted file mode 100644
index 21d1a6a7..00000000
--- a/pkg/component/ref_objects_suite_test.go
+++ /dev/null
@@ -1,381 +0,0 @@
-/*
-Copyright 2021 The KubeVela Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-	http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package component
-
-import (
-	"context"
-	"math/rand"
-	"testing"
-	"time"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	corev1 "k8s.io/api/core/v1"
-	rbacv1 "k8s.io/api/rbac/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	utilfeature "k8s.io/apiserver/pkg/util/feature"
-	"k8s.io/client-go/rest"
-	featuregatetesting "k8s.io/component-base/featuregate/testing"
-	"k8s.io/utils/ptr"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/envtest"
-
-	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
-	"github.com/oam-dev/kubevela/pkg/features"
-	pkgcommon "github.com/oam-dev/kubevela/pkg/utils/common"
-)
-
-var cfg *rest.Config
-var k8sClient client.Client
-var testEnv *envtest.Environment
-
-func TestUtils(t *testing.T) {
-	RegisterFailHandler(Fail)
-	RunSpecs(t, "Utils Suite")
-}
-
-var _ = BeforeSuite(func() {
-	rand.Seed(time.Now().UnixNano())
-	By("bootstrapping test environment for utils test")
-
-	testEnv = &envtest.Environment{
-		ControlPlaneStartTimeout: time.Minute * 3,
-		ControlPlaneStopTimeout:  time.Minute,
-		UseExistingCluster:       ptr.To(false),
-		CRDDirectoryPaths:        []string{"./testdata"},
-	}
-
-	By("start kube test env")
-	var err error
-	cfg, err = testEnv.Start()
-	Expect(err).ShouldNot(HaveOccurred())
-	Expect(cfg).ToNot(BeNil())
-
-	By("new kube client")
-	cfg.Timeout = time.Minute * 2
-	k8sClient, err = client.New(cfg, client.Options{Scheme: pkgcommon.Scheme})
-	Expect(err).Should(Succeed())
-})
-
-var _ = AfterSuite(func() {
-	By("tearing down the test environment")
-	err := testEnv.Stop()
-	Expect(err).ToNot(HaveOccurred())
-})
-
-var _ = Describe("Test ref-objects functions", func() {
-	It("Test SelectRefObjectsForDispatch", func() {
-		featuregatetesting.SetFeatureGateDuringTest(GinkgoT(), utilfeature.DefaultFeatureGate, features.LegacyObjectTypeIdentifier, true)
-		featuregatetesting.SetFeatureGateDuringTest(GinkgoT(), utilfeature.DefaultFeatureGate, features.DeprecatedObjectLabelSelector, true)
-		By("Create objects")
-		Expect(k8sClient.Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}})).Should(Succeed())
-		for _, obj := range []client.Object{&corev1.ConfigMap{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "dynamic",
-				Namespace: "test",
-			},
-		}, &corev1.Service{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:       "dynamic",
-				Namespace:  "test",
-				Generation: int64(5),
-			},
-			Spec: corev1.ServiceSpec{
-				ClusterIP: "10.0.0.254",
-				Ports:     []corev1.ServicePort{{Port: 80}},
-			},
-		}, &corev1.ConfigMap{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "by-label-1",
-				Namespace: "test",
-				Labels:    map[string]string{"key": "value"},
-			},
-		}, &corev1.ConfigMap{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "by-label-2",
-				Namespace: "test",
-				Labels:    map[string]string{"key": "value"},
-			},
-		}, &rbacv1.ClusterRole{
-			ObjectMeta: metav1.ObjectMeta{
-				Name: "test-cluster-role",
-			},
-		}} {
-			Expect(k8sClient.Create(context.Background(), obj)).Should(Succeed())
-		}
-		createUnstructured := func(apiVersion string, kind string, name string, namespace string, labels map[string]interface{}) *unstructured.Unstructured {
-			un := &unstructured.Unstructured{
-				Object: map[string]interface{}{
-					"apiVersion": apiVersion,
-					"kind":       kind,
-					"metadata":   map[string]interface{}{"name": name},
-				},
-			}
-			if namespace != "" {
-				un.SetNamespace(namespace)
-			}
-			if labels != nil {
-				un.Object["metadata"].(map[string]interface{})["labels"] = labels
-			}
-			return un
-		}
-		testcases := map[string]struct {
-			Input         v1alpha1.ObjectReferrer
-			compName      string
-			appNs         string
-			Output        []*unstructured.Unstructured
-			Error         string
-			Scope         string
-			IsService     bool
-			IsClusterRole bool
-		}{
-			"normal": {
-				Input: v1alpha1.ObjectReferrer{
-					ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"},
-					ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic"},
-				},
-				appNs:  "test",
-				Output: []*unstructured.Unstructured{createUnstructured("v1", "ConfigMap", "dynamic", "test", nil)},
-			},
-			"legacy-type-identifier": {
-				Input: v1alpha1.ObjectReferrer{
-					ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{LegacyObjectTypeIdentifier: v1alpha1.LegacyObjectTypeIdentifier{Kind: "ConfigMap", APIVersion: "v1"}},
-					ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic"},
-				},
-				appNs:  "test",
-				Output: []*unstructured.Unstructured{createUnstructured("v1", "ConfigMap", "dynamic", "test", nil)},
-			},
-			"invalid-apiVersion": {
-				Input: v1alpha1.ObjectReferrer{
-					ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{LegacyObjectTypeIdentifier: v1alpha1.LegacyObjectTypeIdentifier{Kind: "ConfigMap", APIVersion: "a/b/v1"}},
-					ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic"},
-				},
-				appNs: "test",
-				Error: "invalid APIVersion",
-			},
-			"invalid-type-identifier": {
-				Input: v1alpha1.ObjectReferrer{
-					ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{},
-					ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic"},
-				},
-				appNs: "test",
-				Error: "neither resource or apiVersion/kind is set",
-			},
-			"name-and-selector-both-set": {
-				Input: v1alpha1.ObjectReferrer{ObjectSelector: v1alpha1.ObjectSelector{Name: "dynamic", LabelSelector: map[string]string{"key": "value"}}},
-				appNs: "test",
-				Error: "invalid object selector for ref-objects, name and labelSelector cannot be both set",
-			},
-			"empty-ref-object-name": {
-				Input:    v1alpha1.ObjectReferrer{ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"}},
-				compName: "dynamic",
-				appNs:    "test",
-				Output:   []*unstructured.Unstructured{createUnstructured("v1", "ConfigMap", "dynamic", "test", nil)},
-			},
-			"cannot-find-ref-object": {
-				Input: v1alpha1.ObjectReferrer{
-					ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"},
-					ObjectSelector:       v1alpha1.ObjectSelector{Name: "static"},
-				},
-				appNs: "test",
-				Error: "failed to load ref object",
-			},
-			"modify-service": {
-				Input: v1alpha1.ObjectReferrer{
-					ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "service"},
-					ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic"},
-				},
-				appNs:     "test",
-				IsService: true,
-			},
-			"by-labels": {
-				Input: v1alpha1.ObjectReferrer{
-					ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"},
-					ObjectSelector:       v1alpha1.ObjectSelector{LabelSelector: map[string]string{"key": "value"}},
-				},
-				appNs: "test",
-				Output: []*unstructured.Unstructured{
-					createUnstructured("v1", "ConfigMap", "by-label-1", "test", map[string]interface{}{"key": "value"}),
-					createUnstructured("v1", "ConfigMap", "by-label-2", "test", map[string]interface{}{"key": "value"}),
-				},
-			},
-			"by-deprecated-labels": {
-				Input: v1alpha1.ObjectReferrer{
-					ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"},
-					ObjectSelector:       v1alpha1.ObjectSelector{DeprecatedLabelSelector: map[string]string{"key": "value"}},
-				},
-				appNs: "test",
-				Output: []*unstructured.Unstructured{
-					createUnstructured("v1", "ConfigMap", "by-label-1", "test", map[string]interface{}{"key": "value"}),
-					createUnstructured("v1", "ConfigMap", "by-label-2", "test", map[string]interface{}{"key": "value"}),
-				},
-			},
-			"no-kind-for-resource": {
-				Input: v1alpha1.ObjectReferrer{ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "unknown"}},
-				appNs: "test",
-				Error: "no matches",
-			},
-			"cross-namespace": {
-				Input: v1alpha1.ObjectReferrer{
-					ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"},
-					ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic", Namespace: "test"},
-				},
-				appNs:  "demo",
-				Output: []*unstructured.Unstructured{createUnstructured("v1", "ConfigMap", "dynamic", "test", nil)},
-			},
-			"cross-namespace-forbidden": {
-				Input: v1alpha1.ObjectReferrer{
-					ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"},
-					ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic", Namespace: "test"},
-				},
-				appNs: "demo",
-				Scope: RefObjectsAvailableScopeNamespace,
-				Error: "cannot refer to objects outside the application's namespace",
-			},
-			"cross-cluster": {
-				Input: v1alpha1.ObjectReferrer{
-					ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"},
-					ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic", Cluster: "demo"},
-				},
-				appNs:  "test",
-				Output: []*unstructured.Unstructured{createUnstructured("v1", "ConfigMap", "dynamic", "test", nil)},
-			},
-			"cross-cluster-forbidden": {
-				Input: v1alpha1.ObjectReferrer{
-					ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"},
-					ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic", Cluster: "demo"},
-				},
-				appNs: "test",
-				Scope: RefObjectsAvailableScopeCluster,
-				Error: "cannot refer to objects outside control plane",
-			},
-			"test-cluster-scope-resource": {
-				Input: v1alpha1.ObjectReferrer{
-					ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "clusterrole"},
-					ObjectSelector:       v1alpha1.ObjectSelector{Name: "test-cluster-role"},
-				},
-				appNs:         "test",
-				Scope:         RefObjectsAvailableScopeCluster,
-				Output:        []*unstructured.Unstructured{createUnstructured("rbac.authorization.k8s.io/v1", "ClusterRole", "test-cluster-role", "", nil)},
-				IsClusterRole: true,
-			},
-		}
-		for name, tt := range testcases {
-			By("Test " + name)
-			if tt.Scope == "" {
-				tt.Scope = RefObjectsAvailableScopeGlobal
-			}
-			RefObjectsAvailableScope = tt.Scope
-			output, err := SelectRefObjectsForDispatch(context.Background(), k8sClient, tt.appNs, tt.compName, tt.Input)
-			if tt.Error != "" {
-				Expect(err).ShouldNot(BeNil())
-				Expect(err.Error()).Should(ContainSubstring(tt.Error))
-			} else {
-				Expect(err).Should(Succeed())
-				if tt.IsService {
-					Expect(output[0].Object["kind"]).Should(Equal("Service"))
-					Expect(output[0].Object["spec"].(map[string]interface{})["clusterIP"]).Should(BeNil())
-				} else {
-					if tt.IsClusterRole {
-						delete(output[0].Object, "rules")
-					}
-					Expect(output).Should(Equal(tt.Output))
-				}
-			}
-		}
-	})
-
-	It("Test AppendUnstructuredObjects", func() {
-		testCases := map[string]struct {
-			Inputs  []*unstructured.Unstructured
-			Input   *unstructured.Unstructured
-			Outputs []*unstructured.Unstructured
-		}{
-			"overlap": {
-				Inputs: []*unstructured.Unstructured{{Object: map[string]interface{}{
-					"apiVersion": "v1",
-					"kind":       "ConfigMap",
-					"metadata":   map[string]interface{}{"name": "x", "namespace": "default"},
-					"data":       "a",
-				}}, {Object: map[string]interface{}{
-					"apiVersion": "v1",
-					"kind":       "ConfigMap",
-					"metadata":   map[string]interface{}{"name": "y", "namespace": "default"},
-					"data":       "b",
-				}}},
-				Input: &unstructured.Unstructured{Object: map[string]interface{}{
-					"apiVersion": "v1",
-					"kind":       "ConfigMap",
-					"metadata":   map[string]interface{}{"name": "y", "namespace": "default"},
-					"data":       "c",
-				}},
-				Outputs: []*unstructured.Unstructured{{Object: map[string]interface{}{
-					"apiVersion": "v1",
-					"kind":       "ConfigMap",
-					"metadata":   map[string]interface{}{"name": "x", "namespace": "default"},
-					"data":       "a",
-				}}, {Object: map[string]interface{}{
-					"apiVersion": "v1",
-					"kind":       "ConfigMap",
-					"metadata":   map[string]interface{}{"name": "y", "namespace": "default"},
-					"data":       "c",
-				}}},
-			},
-			"append": {
-				Inputs: []*unstructured.Unstructured{{Object: map[string]interface{}{
-					"apiVersion": "v1",
-					"kind":       "ConfigMap",
-					"metadata":   map[string]interface{}{"name": "x", "namespace": "default"},
-					"data":       "a",
-				}}, {Object: map[string]interface{}{
-					"apiVersion": "v1",
-					"kind":       "ConfigMap",
-					"metadata":   map[string]interface{}{"name": "y", "namespace": "default"},
-					"data":       "b",
-				}}},
-				Input: &unstructured.Unstructured{Object: map[string]interface{}{
-					"apiVersion": "v1",
-					"kind":       "ConfigMap",
-					"metadata":   map[string]interface{}{"name": "z", "namespace": "default"},
-					"data":       "c",
-				}},
-				Outputs: []*unstructured.Unstructured{{Object: map[string]interface{}{
-					"apiVersion": "v1",
-					"kind":       "ConfigMap",
-					"metadata":   map[string]interface{}{"name": "x", "namespace": "default"},
-					"data":       "a",
-				}}, {Object: map[string]interface{}{
-					"apiVersion": "v1",
-					"kind":       "ConfigMap",
-					"metadata":   map[string]interface{}{"name": "y", "namespace": "default"},
-					"data":       "b",
-				}}, {Object: map[string]interface{}{
-					"apiVersion": "v1",
-					"kind":       "ConfigMap",
-					"metadata":   map[string]interface{}{"name": "z", "namespace": "default"},
-					"data":       "c",
-				}}},
-			},
-		}
-		for name, tt := range testCases {
-			By("Test " + name)
-			Expect(AppendUnstructuredObjects(tt.Inputs, tt.Input)).Should(Equal(tt.Outputs))
-		}
-	})
-
-})
diff --git a/pkg/component/ref_objects_test.go b/pkg/component/ref_objects_test.go
new file mode 100644
index 00000000..c65bf93d
--- /dev/null
+++ b/pkg/component/ref_objects_test.go
@@ -0,0 +1,748 @@
+/*
+Copyright 2021 The KubeVela Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package component
+
+import (
+	"context"
+	"encoding/json"
+	"reflect"
+	"strings"
+	"testing"
+
+	corev1 "k8s.io/api/core/v1"
+	rbacv1 "k8s.io/api/rbac/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	utilfeature "k8s.io/apiserver/pkg/util/feature"
+	featuregatetesting "k8s.io/component-base/featuregate/testing"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
+	"github.com/oam-dev/kubevela/pkg/features"
+)
+
+func TestGetLabelSelectorFromRefObjectSelector(t *testing.T) {
+	type args struct {
+		selector v1alpha1.ObjectReferrer
+	}
+	tests := []struct {
+		name      string
+		args      args
+		featureOn bool
+		want      map[string]string
+	}{
+		{
+			name: "label selector present",
+			args: args{
+				selector: v1alpha1.ObjectReferrer{
+					ObjectSelector: v1alpha1.ObjectSelector{
+						LabelSelector: map[string]string{"app": "my-app"},
+					},
+				},
+			},
+			want: map[string]string{"app": "my-app"},
+		},
+		{
+			name: "deprecated label selector present and feature on",
+			args: args{
+				selector: v1alpha1.ObjectReferrer{
+					ObjectSelector: v1alpha1.ObjectSelector{
+						DeprecatedLabelSelector: map[string]string{"app": "my-app-deprecated"},
+					},
+				},
+			},
+			featureOn: true,
+			want:      map[string]string{"app": "my-app-deprecated"},
+		},
+		{
+			name: "deprecated label selector present and feature off",
+			args: args{
+				selector: v1alpha1.ObjectReferrer{
+					ObjectSelector: v1alpha1.ObjectSelector{
+						DeprecatedLabelSelector: map[string]string{"app": "my-app-deprecated"},
+					},
+				},
+			},
+			featureOn: false,
+			want:      nil,
+		},
+		{
+			name: "both present, label selector takes precedence",
+			args: args{
+				selector: v1alpha1.ObjectReferrer{
+					ObjectSelector: v1alpha1.ObjectSelector{
+						LabelSelector:           map[string]string{"app": "my-app"},
+						DeprecatedLabelSelector: map[string]string{"app": "my-app-deprecated"},
+					},
+				},
+			},
+			featureOn: true,
+			want:      map[string]string{"app": "my-app"},
+		},
+		{
+			name: "no selector",
+			args: args{
+				selector: v1alpha1.ObjectReferrer{},
+			},
+			want: nil,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DeprecatedObjectLabelSelector, tt.featureOn)
+			if got := GetLabelSelectorFromRefObjectSelector(tt.args.selector); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("GetLabelSelectorFromRefObjectSelector() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestValidateRefObjectSelector(t *testing.T) {
+	type args struct {
+		selector v1alpha1.ObjectReferrer
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "name only",
+			args: args{
+				selector: v1alpha1.ObjectReferrer{
+					ObjectSelector: v1alpha1.ObjectSelector{
+						Name: "my-obj",
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "label selector only",
+			args: args{
+				selector: v1alpha1.ObjectReferrer{
+					ObjectSelector: v1alpha1.ObjectSelector{
+						LabelSelector: map[string]string{"app": "my-app"},
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "both name and label selector",
+			args: args{
+				selector: v1alpha1.ObjectReferrer{
+					ObjectSelector: v1alpha1.ObjectSelector{
+						Name:          "my-obj",
+						LabelSelector: map[string]string{"app": "my-app"},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "empty selector",
+			args: args{
+				selector: v1alpha1.ObjectReferrer{},
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := ValidateRefObjectSelector(tt.args.selector); (err != nil) != tt.wantErr {
+				t.Errorf("ValidateRefObjectSelector() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestClearRefObjectForDispatch(t *testing.T) {
+	un := &unstructured.Unstructured{
+		Object: map[string]interface{}{
+			"metadata": map[string]interface{}{
+				"name":              "test-obj",
+				"namespace":         "default",
+				"resourceVersion":   "12345",
+				"generation":        int64(1),
+				"uid":               "abc-def",
+				"creationTimestamp": "2021-01-01T00:00:00Z",
+				"managedFields":     []interface{}{},
+				"ownerReferences":   []interface{}{},
+			},
+			"status": map[string]interface{}{
+				"phase": "Available",
+			},
+		},
+	}
+	ClearRefObjectForDispatch(un)
+
+	if un.GetResourceVersion() != "" {
+		t.Errorf("resourceVersion should be cleared")
+	}
+	if un.GetGeneration() != 0 {
+		t.Errorf("generation should be cleared")
+	}
+	if len(un.GetOwnerReferences()) != 0 {
+		t.Errorf("ownerReferences should be cleared")
+	}
+	if un.GetDeletionTimestamp() != nil {
+		t.Errorf("deletionTimestamp should be nil")
+	}
+	if len(un.GetManagedFields()) != 0 {
+		t.Errorf("managedFields should be cleared")
+	}
+	if un.GetUID() != "" {
+		t.Errorf("uid should be cleared")
+	}
+	if _, found, _ := unstructured.NestedFieldNoCopy(un.Object, "metadata", "creationTimestamp"); found {
+		t.Errorf("creationTimestamp should be removed")
+	}
+	if _, found, _ := unstructured.NestedFieldNoCopy(un.Object, "status"); found {
+		t.Errorf("status should be removed")
+	}
+
+	// Test for service
+	svc := &unstructured.Unstructured{
+		Object: map[string]interface{}{
+			"apiVersion": "v1",
+			"kind":       "Service",
+			"metadata": map[string]interface{}{
+				"name": "test-svc",
+			},
+			"spec": map[string]interface{}{
+				"clusterIP":  "1.2.3.4",
+				"clusterIPs": []interface{}{"1.2.3.4"},
+			},
+		},
+	}
+	ClearRefObjectForDispatch(svc)
+	if _, found, _ := unstructured.NestedString(svc.Object, "spec", "clusterIP"); found {
+		t.Errorf("service clusterIP should be removed")
+	}
+	if _, found, _ := unstructured.NestedStringSlice(svc.Object, "spec", "clusterIPs"); found {
+		t.Errorf("service clusterIPs should be removed")
+	}
+
+	// Test for service with ClusterIPNone
+	svcNone := &unstructured.Unstructured{
+		Object: map[string]interface{}{
+			"apiVersion": "v1",
+			"kind":       "Service",
+			"metadata": map[string]interface{}{
+				"name": "test-svc-none",
+			},
+			"spec": map[string]interface{}{
+				"clusterIP": corev1.ClusterIPNone,
+			},
+		},
+	}
+	ClearRefObjectForDispatch(svcNone)
+	if ip, found, _ := unstructured.NestedString(svcNone.Object, "spec", "clusterIP"); !found || ip != corev1.ClusterIPNone {
+		t.Errorf("service with clusterIP None should not be removed")
+	}
+}
+
+func TestSelectRefObjectsForDispatch(t *testing.T) {
+	featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LegacyObjectTypeIdentifier, true)
+	featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DeprecatedObjectLabelSelector, true)
+	t.Log("Create objects")
+	if err := k8sClient.Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}}); err != nil {
+		t.Fatal(err)
+	}
+	for _, obj := range []client.Object{&corev1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "dynamic",
+			Namespace: "test",
+		},
+	}, &corev1.Service{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:       "dynamic",
+			Namespace:  "test",
+			Generation: int64(5),
+		},
+		Spec: corev1.ServiceSpec{
+			ClusterIP: "10.0.0.254",
+			Ports:     []corev1.ServicePort{{Port: 80}},
+		},
+	}, &corev1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "by-label-1",
+			Namespace: "test",
+			Labels:    map[string]string{"key": "value"},
+		},
+	}, &corev1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "by-label-2",
+			Namespace: "test",
+			Labels:    map[string]string{"key": "value"},
+		},
+	}, &rbacv1.ClusterRole{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "test-cluster-role",
+		},
+	}} {
+		if err := k8sClient.Create(context.Background(), obj); err != nil {
+			t.Fatal(err)
+		}
+	}
+	createUnstructured := func(apiVersion string, kind string, name string, namespace string, labels map[string]interface{}) *unstructured.Unstructured {
+		un := &unstructured.Unstructured{
+			Object: map[string]interface{}{"apiVersion": apiVersion,
+				"kind":     kind,
+				"metadata": map[string]interface{}{"name": name},
+			},
+		}
+		if namespace != "" {
+			un.SetNamespace(namespace)
+		}
+		if labels != nil {
+			un.Object["metadata"].(map[string]interface{})["labels"] = labels
+		}
+		return un
+	}
+	testcases := map[string]struct {
+		Input         v1alpha1.ObjectReferrer
+		compName      string
+		appNs         string
+		Output        []*unstructured.Unstructured
+		Error         string
+		Scope         string
+		IsService     bool
+		IsClusterRole bool
+	}{
+		"normal": {
+			Input: v1alpha1.ObjectReferrer{
+				ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"},
+				ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic"},
+			},
+			appNs:  "test",
+			Output: []*unstructured.Unstructured{createUnstructured("v1", "ConfigMap", "dynamic", "test", nil)},
+		},
+		"legacy-type-identifier": {
+			Input: v1alpha1.ObjectReferrer{
+				ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{LegacyObjectTypeIdentifier: v1alpha1.LegacyObjectTypeIdentifier{Kind: "ConfigMap", APIVersion: "v1"}},
+				ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic"},
+			},
+			appNs:  "test",
+			Output: []*unstructured.Unstructured{createUnstructured("v1", "ConfigMap", "dynamic", "test", nil)},
+		},
+		"invalid-apiVersion": {
+			Input: v1alpha1.ObjectReferrer{
+				ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{LegacyObjectTypeIdentifier: v1alpha1.LegacyObjectTypeIdentifier{Kind: "ConfigMap", APIVersion: "a/b/v1"}},
+				ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic"},
+			},
+			appNs: "test",
+			Error: "invalid APIVersion",
+		},
+		"invalid-type-identifier": {
+			Input: v1alpha1.ObjectReferrer{
+				ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{},
+				ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic"},
+			},
+			appNs: "test",
+			Error: "neither resource or apiVersion/kind is set",
+		},
+		"name-and-selector-both-set": {
+			Input: v1alpha1.ObjectReferrer{ObjectSelector: v1alpha1.ObjectSelector{Name: "dynamic", LabelSelector: map[string]string{"key": "value"}}},
+			appNs: "test",
+			Error: "invalid object selector for ref-objects, name and labelSelector cannot be both set",
+		},
+		"empty-ref-object-name": {
+			Input:    v1alpha1.ObjectReferrer{ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"}},
+			compName: "dynamic",
+			appNs:    "test",
+			Output:   []*unstructured.Unstructured{createUnstructured("v1", "ConfigMap", "dynamic", "test", nil)},
+		},
+		"cannot-find-ref-object": {
+			Input: v1alpha1.ObjectReferrer{
+				ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"},
+				ObjectSelector:       v1alpha1.ObjectSelector{Name: "static"},
+			},
+			appNs: "test",
+			Error: "failed to load ref object",
+		},
+		"modify-service": {
+			Input: v1alpha1.ObjectReferrer{
+				ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "service"},
+				ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic"},
+			},
+			appNs:     "test",
+			IsService: true,
+		},
+		"by-labels": {
+			Input: v1alpha1.ObjectReferrer{
+				ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"},
+				ObjectSelector:       v1alpha1.ObjectSelector{LabelSelector: map[string]string{"key": "value"}},
+			},
+			appNs: "test",
+			Output: []*unstructured.Unstructured{
+				createUnstructured("v1", "ConfigMap", "by-label-1", "test", map[string]interface{}{"key": "value"}),
+				createUnstructured("v1", "ConfigMap", "by-label-2", "test", map[string]interface{}{"key": "value"}),
+			},
+		},
+		"by-deprecated-labels": {
+			Input: v1alpha1.ObjectReferrer{
+				ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"},
+				ObjectSelector:       v1alpha1.ObjectSelector{DeprecatedLabelSelector: map[string]string{"key": "value"}},
+			},
+			appNs: "test",
+			Output: []*unstructured.Unstructured{
+				createUnstructured("v1", "ConfigMap", "by-label-1", "test", map[string]interface{}{"key": "value"}),
+				createUnstructured("v1", "ConfigMap", "by-label-2", "test", map[string]interface{}{"key": "value"}),
+			},
+		},
+		"no-kind-for-resource": {
+			Input: v1alpha1.ObjectReferrer{ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "unknown"}},
+			appNs: "test",
+			Error: "no matches",
+		},
+		"cross-namespace": {
+			Input: v1alpha1.ObjectReferrer{
+				ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"},
+				ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic", Namespace: "test"},
+			},
+			appNs:  "demo",
+			Output: []*unstructured.Unstructured{createUnstructured("v1", "ConfigMap", "dynamic", "test", nil)},
+		},
+		"cross-namespace-forbidden": {
+			Input: v1alpha1.ObjectReferrer{
+				ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"},
+				ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic", Namespace: "test"},
+			},
+			appNs: "demo",
+			Scope: RefObjectsAvailableScopeNamespace,
+			Error: "cannot refer to objects outside the application's namespace",
+		},
+		"cross-cluster": {
+			Input: v1alpha1.ObjectReferrer{
+				ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"},
+				ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic", Cluster: "demo"},
+			},
+			appNs:  "test",
+			Output: []*unstructured.Unstructured{createUnstructured("v1", "ConfigMap", "dynamic", "test", nil)},
+		},
+		"cross-cluster-forbidden": {
+			Input: v1alpha1.ObjectReferrer{
+				ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"},
+				ObjectSelector:       v1alpha1.ObjectSelector{Name: "dynamic", Cluster: "demo"},
+			},
+			appNs: "test",
+			Scope: RefObjectsAvailableScopeCluster,
+			Error: "cannot refer to objects outside control plane",
+		},
+		"test-cluster-scope-resource": {
+			Input: v1alpha1.ObjectReferrer{
+				ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "clusterrole"},
+				ObjectSelector:       v1alpha1.ObjectSelector{Name: "test-cluster-role"},
+			},
+			appNs:         "test",
+			Scope:         RefObjectsAvailableScopeCluster,
+			Output:        []*unstructured.Unstructured{createUnstructured("rbac.authorization.k8s.io/v1", "ClusterRole", "test-cluster-role", "", nil)},
+			IsClusterRole: true,
+		},
+	}
+	for name, tt := range testcases {
+		t.Run(name, func(t *testing.T) {
+			if tt.Scope == "" {
+				tt.Scope = RefObjectsAvailableScopeGlobal
+			}
+			RefObjectsAvailableScope = tt.Scope
+			output, err := SelectRefObjectsForDispatch(context.Background(), k8sClient, tt.appNs, tt.compName, tt.Input)
+			if tt.Error != "" {
+				if err == nil {
+					t.Fatalf("expected error, got nil")
+				}
+				if !strings.Contains(err.Error(), tt.Error) {
+					t.Fatalf("expected error message to contain %q, got %q", tt.Error, err.Error())
+				}
+			} else {
+				if err != nil {
+					t.Fatal(err)
+				}
+				if tt.IsService {
+					if output[0].Object["kind"] != "Service" {
+						t.Fatalf(`expected kind to be "Service", got %q`, output[0].Object["kind"])
+					}
+					if output[0].Object["spec"].(map[string]interface{})["clusterIP"] != nil {
+						t.Fatalf(`expected clusterIP to be nil, got %v`, output[0].Object["spec"].(map[string]interface{})["clusterIP"])
+					}
+				} else {
+					if tt.IsClusterRole {
+						delete(output[0].Object, "rules")
+					}
+					if !reflect.DeepEqual(output, tt.Output) {
+						t.Fatalf("expected output to be %v, got %v", tt.Output, output)
+					}
+				}
+			}
+		})
+	}
+}
+
+func TestReferredObjectsDelegatingClient(t *testing.T) {
+	objs := []*unstructured.Unstructured{
+		{
+			Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "ConfigMap",
+				"metadata": map[string]interface{}{
+					"name":      "cm-1",
+					"namespace": "ns-1",
+					"labels":    map[string]interface{}{"app": "app-1"},
+				},
+			},
+		},
+		{
+			Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "ConfigMap",
+				"metadata": map[string]interface{}{
+					"name":      "cm-2",
+					"namespace": "ns-1",
+					"labels":    map[string]interface{}{"app": "app-2"},
+				},
+			},
+		},
+		{
+			Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "Secret",
+				"metadata": map[string]interface{}{
+					"name":      "secret-1",
+					"namespace": "ns-1",
+					"labels":    map[string]interface{}{"app": "app-1"},
+				},
+			},
+		},
+	}
+
+	delegatingClient := ReferredObjectsDelegatingClient(k8sClient, objs)
+
+	t.Run("Get", func(t *testing.T) {
+		t.Run("should get existing object", func(t *testing.T) {
+			obj := &unstructured.Unstructured{}
+			obj.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMap"})
+			err := delegatingClient.Get(context.Background(), client.ObjectKey{Name: "cm-1", Namespace: "ns-1"}, obj)
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+			if obj.GetName() != "cm-1" {
+				t.Errorf("expected object name cm-1, got %s", obj.GetName())
+			}
+		})
+
+		t.Run("should return not found for non-existing object", func(t *testing.T) {
+			obj := &unstructured.Unstructured{}
+			obj.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMap"})
+			err := delegatingClient.Get(context.Background(), client.ObjectKey{Name: "cm-non-existent", Namespace: "ns-1"}, obj)
+			if !apierrors.IsNotFound(err) {
+				t.Errorf("expected not found error, got %v", err)
+			}
+		})
+
+		t.Run("should return not found for different kind", func(t *testing.T) {
+			obj := &unstructured.Unstructured{}
+			obj.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"})
+			err := delegatingClient.Get(context.Background(), client.ObjectKey{Name: "cm-1", Namespace: "ns-1"}, obj)
+			if !apierrors.IsNotFound(err) {
+				t.Errorf("expected not found error, got %v", err)
+			}
+		})
+	})
+
+	t.Run("List", func(t *testing.T) {
+		t.Run("should list all objects of a kind", func(t *testing.T) {
+			list := &unstructured.UnstructuredList{}
+			list.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMapList"})
+			err := delegatingClient.List(context.Background(), list)
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+			if len(list.Items) != 2 {
+				t.Errorf("expected 2 items, got %d", len(list.Items))
+			}
+		})
+
+		t.Run("should list with namespace", func(t *testing.T) {
+			list := &unstructured.UnstructuredList{}
+			list.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMapList"})
+			err := delegatingClient.List(context.Background(), list, client.InNamespace("ns-1"))
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+			if len(list.Items) != 2 {
+				t.Errorf("expected 2 items, got %d", len(list.Items))
+			}
+
+			list.Items = []unstructured.Unstructured{}
+			err = delegatingClient.List(context.Background(), list, client.InNamespace("ns-2"))
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+			if len(list.Items) != 0 {
+				t.Errorf("expected 0 items, got %d", len(list.Items))
+			}
+		})
+
+		t.Run("should list with label selector", func(t *testing.T) {
+			list := &unstructured.UnstructuredList{}
+			list.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMapList"})
+			err := delegatingClient.List(context.Background(), list, client.MatchingLabels{"app": "app-1"})
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+			if len(list.Items) != 1 {
+				t.Errorf("expected 1 item, got %d", len(list.Items))
+			}
+			if list.Items[0].GetName() != "cm-1" {
+				t.Errorf("expected cm-1, got %s", list.Items[0].GetName())
+			}
+		})
+	})
+}
+
+func TestAppendUnstructuredObjects(t *testing.T) {
+	testCases := map[string]struct {
+		Inputs  []*unstructured.Unstructured
+		Input   *unstructured.Unstructured
+		Outputs []*unstructured.Unstructured
+	}{
+		"overlap": {
+			Inputs: []*unstructured.Unstructured{{Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "ConfigMap",
+				"metadata":   map[string]interface{}{"name": "x", "namespace": "default"},
+				"data":       "a",
+			}}, {Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "ConfigMap",
+				"metadata":   map[string]interface{}{"name": "y", "namespace": "default"},
+				"data":       "b",
+			}}},
+			Input: &unstructured.Unstructured{Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "ConfigMap",
+				"metadata":   map[string]interface{}{"name": "y", "namespace": "default"},
+				"data":       "c",
+			}},
+			Outputs: []*unstructured.Unstructured{{Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "ConfigMap",
+				"metadata":   map[string]interface{}{"name": "x", "namespace": "default"},
+				"data":       "a",
+			}}, {Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "ConfigMap",
+				"metadata":   map[string]interface{}{"name": "y", "namespace": "default"},
+				"data":       "c",
+			}}},
+		},
+		"append": {
+			Inputs: []*unstructured.Unstructured{{Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "ConfigMap",
+				"metadata":   map[string]interface{}{"name": "x", "namespace": "default"},
+				"data":       "a",
+			}}, {Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "ConfigMap",
+				"metadata":   map[string]interface{}{"name": "y", "namespace": "default"},
+				"data":       "b",
+			}}},
+			Input: &unstructured.Unstructured{Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "ConfigMap",
+				"metadata":   map[string]interface{}{"name": "z", "namespace": "default"},
+				"data":       "c",
+			}},
+			Outputs: []*unstructured.Unstructured{{Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "ConfigMap",
+				"metadata":   map[string]interface{}{"name": "x", "namespace": "default"},
+				"data":       "a",
+			}}, {Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "ConfigMap",
+				"metadata":   map[string]interface{}{"name": "y", "namespace": "default"},
+				"data":       "b",
+			}}, {Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "ConfigMap",
+				"metadata":   map[string]interface{}{"name": "z", "namespace": "default"},
+				"data":       "c",
+			}}},
+		},
+	}
+	for name, tt := range testCases {
+		t.Run(name, func(t *testing.T) {
+			got := AppendUnstructuredObjects(tt.Inputs, tt.Input)
+			if !reflect.DeepEqual(got, tt.Outputs) {
+				t.Fatalf("expected output to be %v, got %v", tt.Outputs, got)
+			}
+		})
+	}
+}
+
+func TestConvertUnstructuredsToReferredObjects(t *testing.T) {
+	uns := []*unstructured.Unstructured{
+		{
+			Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "ConfigMap",
+				"metadata": map[string]interface{}{
+					"name": "cm-1",
+				},
+			},
+		},
+		{
+			Object: map[string]interface{}{
+				"apiVersion": "apps/v1",
+				"kind":       "Deployment",
+				"metadata": map[string]interface{}{
+					"name": "deploy-1",
+				},
+			},
+		},
+	}
+
+	refObjs, err := ConvertUnstructuredsToReferredObjects(uns)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+
+	if len(refObjs) != 2 {
+		t.Fatalf("expected 2 referred objects, got %d", len(refObjs))
+	}
+
+	for i, un := range uns {
+		raw, err := json.Marshal(un)
+		if err != nil {
+			t.Fatalf("failed to marshal unstructured: %v", err)
+		}
+		expected := common.ReferredObject{
+			RawExtension: runtime.RawExtension{Raw: raw},
+		}
+		if !reflect.DeepEqual(refObjs[i], expected) {
+			t.Errorf("expected refObj %v, got %v", expected, refObjs[i])
+		}
+	}
+}
diff --git a/pkg/controller/core.oam.dev/v1beta1/application/generator_test.go b/pkg/controller/core.oam.dev/v1beta1/application/generator_test.go
index 06a38a85..7314c6dc 100644
--- a/pkg/controller/core.oam.dev/v1beta1/application/generator_test.go
+++ b/pkg/controller/core.oam.dev/v1beta1/application/generator_test.go
@@ -289,4 +289,31 @@ var _ = Describe("Test Application workflow generator", func() {
 		_, _, err = handler.GenerateApplicationSteps(logCtx, app, appParser, af)
 		Expect(err).NotTo(BeNil())
 	})
+
+	It("Test workflow context contains app labels and annotations", func() {
+		app := &oamcore.Application{
+			TypeMeta: metav1.TypeMeta{Kind: "Application", APIVersion: "core.oam.dev/v1beta1"},
+			ObjectMeta: metav1.ObjectMeta{
+				Name:        "app-with-meta",
+				Namespace:   namespaceName,
+				Labels:      map[string]string{"team": "platform", "env": "prod"},
+				Annotations: map[string]string{"description": "meta test", "owner": "sre"},
+			},
+			Spec: oamcore.ApplicationSpec{Components: []common.ApplicationComponent{}},
+		}
+		ctxData := generateContextDataFromApp(app, "apprev-with-meta")
+		Expect(ctxData.AppLabels).To(Equal(app.Labels))
+		Expect(ctxData.AppAnnotations).To(Equal(app.Annotations))
+	})
+
+	It("Test workflow context empty labels annotations", func() {
+		app := &oamcore.Application{
+			TypeMeta:   metav1.TypeMeta{Kind: "Application", APIVersion: "core.oam.dev/v1beta1"},
+			ObjectMeta: metav1.ObjectMeta{Name: "app-without-meta", Namespace: namespaceName},
+			Spec:       oamcore.ApplicationSpec{Components: []common.ApplicationComponent{}},
+		}
+		ctxData := generateContextDataFromApp(app, "apprev-without-meta")
+		Expect(ctxData.AppLabels).To(BeNil())
+		Expect(ctxData.AppAnnotations).To(BeNil())
+	})
 })
diff --git a/pkg/cue/definition/template_test.go b/pkg/cue/definition/template_test.go
index 15a9f5bf..dfd5965d 100644
--- a/pkg/cue/definition/template_test.go
+++ b/pkg/cue/definition/template_test.go
@@ -725,7 +725,7 @@ parameter: {
         }
       }
       outputs: ingress: {
-        apiVersion: "networking.k8s.io/v1beta1"
+        apiVersion: "networking.k8s.io/v1"
         kind: "Ingress"
         metadata:
           name: context.name
@@ -736,9 +736,14 @@ parameter: {
             http: {
               paths: [{
                   path: parameter.path
+                  pathType: "Prefix"
                   backend: {
-                    serviceName: context.name
-                    servicePort: parameter.exposePort
+                    service: {
+                      name: context.name
+                      port: {
+                        number: parameter.exposePort
+                      }
+                    }
                   }
               }]
             }
@@ -781,7 +786,7 @@ parameter: {
 						"metadata":   map[string]interface{}{"name": "testgame-config"}, "data": map[string]interface{}{"enemies": "enemies-data", "lives": "lives-data"}},
 				},
 				"t3service": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "v1", "kind": "Service", "spec": map[string]interface{}{"ports": []interface{}{map[string]interface{}{"port": int64(1080), "targetPort": int64(443)}}, "selector": map[string]interface{}{"app": "test"}}}},
-				"t3ingress": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "networking.k8s.io/v1beta1", "kind": "Ingress", "labels": map[string]interface{}{"config": "enemies-data"}, "metadata": map[string]interface{}{"name": "test"}, "spec": map[string]interface{}{"rules": []interface{}{map[string]interface{}{"host": "example.com", "http": map[string]interface{}{"paths": []interface{}{map[string]interface{}{"backend": map[string]interface{}{"serviceName": "test", "servicePort": int64(1080)}, "path": "ping"}}}}}}}},
+				"t3ingress": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "networking.k8s.io/v1", "kind": "Ingress", "labels": map[string]interface{}{"config": "enemies-data"}, "metadata": map[string]interface{}{"name": "test"}, "spec": map[string]interface{}{"rules": []interface{}{map[string]interface{}{"host": "example.com", "http": map[string]interface{}{"paths": []interface{}{map[string]interface{}{"path": "ping", "pathType": "Prefix", "backend": map[string]interface{}{"service": map[string]interface{}{"name": "test", "port": map[string]interface{}{"number": int64(1080)}}}}}}}}}}},
 			},
 		},
 		"outputs trait with schema": {
diff --git a/pkg/multicluster/cluster_management_test.go b/pkg/multicluster/cluster_management_test.go
new file mode 100644
index 00000000..4a719983
--- /dev/null
+++ b/pkg/multicluster/cluster_management_test.go
@@ -0,0 +1,1088 @@
+/*
+Copyright 2021 The KubeVela Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package multicluster
+
+import (
+	"context"
+	"os"
+	"path/filepath"
+	"testing"
+
+	clusterv1alpha1 "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
+	clustercommon "github.com/oam-dev/cluster-gateway/pkg/common"
+	"github.com/pkg/errors"
+	"github.com/stretchr/testify/require"
+	corev1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
+	ocmclusterv1 "open-cluster-management.io/api/cluster/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
+
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
+)
+
+func TestKubeClusterConfig_SetClusterName(t *testing.T) {
+	testCases := []struct {
+		name         string
+		initialName  string
+		newName      string
+		expectedName string
+	}{
+		{
+			name:         "Non-empty name",
+			initialName:  "old",
+			newName:      "new",
+			expectedName: "new",
+		},
+		{
+			name:         "Empty name",
+			initialName:  "old",
+			newName:      "",
+			expectedName: "old",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			cfg := &KubeClusterConfig{ClusterName: tc.initialName}
+			out := cfg.SetClusterName(tc.newName)
+			require.Equal(t, cfg, out)
+			require.Equal(t, tc.expectedName, cfg.ClusterName)
+		})
+	}
+}
+
+func TestKubeClusterConfig_SetCreateNamespace(t *testing.T) {
+	cfg := &KubeClusterConfig{}
+
+	out := cfg.SetCreateNamespace("ns-1")
+	require.Equal(t, cfg, out)
+	require.Equal(t, "ns-1", cfg.CreateNamespace)
+
+	out = cfg.SetCreateNamespace("")
+	require.Equal(t, cfg, out)
+	require.Equal(t, "", cfg.CreateNamespace)
+}
+
+func TestKubeClusterConfig_Validate(t *testing.T) {
+	testCases := []struct {
+		name        string
+		clusterName string
+		expectErr   bool
+	}{
+		{
+			name:        "Empty name",
+			clusterName: "",
+			expectErr:   true,
+		},
+		{
+			name:        "Local name",
+			clusterName: ClusterLocalName,
+			expectErr:   true,
+		},
+		{
+			name:        "Valid name",
+			clusterName: "prod",
+			expectErr:   false,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			cfg := &KubeClusterConfig{ClusterName: tc.clusterName}
+			err := cfg.Validate()
+			if tc.expectErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func newTestScheme() *runtime.Scheme {
+	s := runtime.NewScheme()
+	_ = corev1.AddToScheme(s)
+	_ = v1beta1.AddToScheme(s)
+	_ = ocmclusterv1.AddToScheme(s)
+	return s
+}
+
+// mockClient is a mock implementation of client.Client for testing.
+// It allows injecting errors for different client operations.
+type mockClient struct {
+	client.Client
+	listErr   error
+	deleteErr error
+	createErr error
+	getErr    error
+	updateErr error
+}
+
+func (m *mockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
+	if m.getErr != nil {
+		return m.getErr
+	}
+	return m.Client.Get(ctx, key, obj, opts...)
+}
+
+func (m *mockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
+	if m.listErr != nil {
+		if _, ok := list.(*v1beta1.ResourceTrackerList); ok {
+			return m.listErr
+		}
+	}
+	return m.Client.List(ctx, list, opts...)
+}
+
+func (m *mockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
+	if m.deleteErr != nil {
+		if _, ok := obj.(*ocmclusterv1.ManagedCluster); ok {
+			return m.deleteErr
+		}
+		if _, ok := obj.(*corev1.Secret); ok {
+			return m.deleteErr
+		}
+	}
+	return m.Client.Delete(ctx, obj, opts...)
+}
+
+func (m *mockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
+	if m.createErr != nil {
+		return m.createErr
+	}
+	return m.Client.Create(ctx, obj, opts...)
+}
+
+func (m *mockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
+	if m.updateErr != nil {
+		return m.updateErr
+	}
+	return m.Client.Update(ctx, obj, opts...)
+}
+
+func makeBaseClusterConfig(clusterName string) *KubeClusterConfig {
+	return &KubeClusterConfig{
+		FilePath:        "",
+		ClusterName:     clusterName,
+		CreateNamespace: "", // avoid PostRegistration side effects
+		Config:          &clientcmdapi.Config{},
+		Cluster: &clientcmdapi.Cluster{
+			Server:                   "https://example:6443",
+			CertificateAuthorityData: []byte("ca-bytes"),
+			InsecureSkipTLSVerify:    false,
+		},
+		AuthInfo: &clientcmdapi.AuthInfo{},
+	}
+}
+
+func TestRegisterByVelaSecret(t *testing.T) {
+	ctx := context.Background()
+	scheme := newTestScheme()
+	oldNS := ClusterGatewaySecretNamespace
+	ClusterGatewaySecretNamespace = "vela-system"
+	t.Cleanup(func() { ClusterGatewaySecretNamespace = oldNS })
+
+	testCases := []struct {
+		name      string
+		cfg       *KubeClusterConfig
+		cli       client.Client
+		expectErr bool
+		verify    func(t *testing.T, cli client.Client, cfg *KubeClusterConfig)
+	}{
+		{
+			name: "Token and endpoint",
+			cfg: func() *KubeClusterConfig {
+				cfg := makeBaseClusterConfig("c-token")
+				cfg.AuthInfo.Token = "my-token"
+				return cfg
+			}(),
+			cli: fake.NewClientBuilder().WithScheme(scheme).Build(),
+			verify: func(t *testing.T, cli client.Client, cfg *KubeClusterConfig) {
+				var sec corev1.Secret
+				require.NoError(t, cli.Get(ctx, client.ObjectKey{Name: cfg.ClusterName, Namespace: ClusterGatewaySecretNamespace}, &sec))
+				require.Equal(t, []byte("my-token"), sec.Data["token"])
+				require.Equal(t, []byte("https://example:6443"), sec.Data["endpoint"])
+				require.Equal(t, []byte("ca-bytes"), sec.Data["ca.crt"])
+				require.Equal(t, string(clusterv1alpha1.CredentialTypeServiceAccountToken), sec.Labels[clustercommon.LabelKeyClusterCredentialType])
+			},
+		},
+		{
+			name: "Token no CA when insecure",
+			cfg: func() *KubeClusterConfig {
+				cfg := makeBaseClusterConfig("c-token-insecure")
+				cfg.Cluster.InsecureSkipTLSVerify = true
+				cfg.AuthInfo.Token = "tok"
+				return cfg
+			}(),
+			cli: fake.NewClientBuilder().WithScheme(scheme).Build(),
+			verify: func(t *testing.T, cli client.Client, cfg *KubeClusterConfig) {
+				var sec corev1.Secret
+				require.NoError(t, cli.Get(ctx, client.ObjectKey{Name: cfg.ClusterName, Namespace: ClusterGatewaySecretNamespace}, &sec))
+				require.Nil(t, sec.Data["ca.crt"])
+			},
+		},
+		{
+			name: "Exec success",
+			cfg: func() *KubeClusterConfig {
+				dir := t.TempDir()
+				script := filepath.Join(dir, "print-token.sh")
+				require.NoError(t, os.WriteFile(script, []byte("#!/usr/bin/env bash\necho '{\"status\":{\"token\":\"exec-token\"}}'\n"), 0755))
+				cfg := makeBaseClusterConfig("c-exec")
+				cfg.AuthInfo.Exec = &clientcmdapi.ExecConfig{Command: script}
+				return cfg
+			}(),
+			cli: fake.NewClientBuilder().WithScheme(scheme).Build(),
+			verify: func(t *testing.T, cli client.Client, cfg *KubeClusterConfig) {
+				var sec corev1.Secret
+				require.NoError(t, cli.Get(ctx, client.ObjectKey{Name: cfg.ClusterName, Namespace: ClusterGatewaySecretNamespace}, &sec))
+				require.Equal(t, []byte("exec-token"), sec.Data["token"])
+				require.Equal(t, string(clusterv1alpha1.CredentialTypeServiceAccountToken), sec.Labels[clustercommon.LabelKeyClusterCredentialType])
+			},
+		},
+		{
+			name: "Exec failure",
+			cfg: func() *KubeClusterConfig {
+				dir := t.TempDir()
+				cfg := makeBaseClusterConfig("c-exec-fail")
+				cfg.AuthInfo.Exec = &clientcmdapi.ExecConfig{Command: filepath.Join(dir, "fail.sh")}
+				require.NoError(t, os.WriteFile(cfg.AuthInfo.Exec.Command, []byte("#!/usr/bin/env bash\nexit 1\n"), 0755))
+				return cfg
+			}(),
+			cli:       fake.NewClientBuilder().WithScheme(scheme).Build(),
+			expectErr: true,
+		},
+		{
+			name: "X509 and proxy",
+			cfg: func() *KubeClusterConfig {
+				cfg := makeBaseClusterConfig("c-x509")
+				cfg.AuthInfo.ClientCertificateData = []byte("crt")
+				cfg.AuthInfo.ClientKeyData = []byte("key")
+				cfg.Cluster.ProxyURL = "http://proxy.example:8080"
+				return cfg
+			}(),
+			cli: fake.NewClientBuilder().WithScheme(scheme).Build(),
+			verify: func(t *testing.T, cli client.Client, cfg *KubeClusterConfig) {
+				var sec corev1.Secret
+				require.NoError(t, cli.Get(ctx, client.ObjectKey{Name: cfg.ClusterName, Namespace: ClusterGatewaySecretNamespace}, &sec))
+				require.Equal(t, []byte("crt"), sec.Data["tls.crt"])
+				require.Equal(t, []byte("key"), sec.Data["tls.key"])
+				require.Equal(t, []byte("http://proxy.example:8080"), sec.Data["proxy-url"])
+				require.Equal(t, string(clusterv1alpha1.CredentialTypeX509Certificate), sec.Labels[clustercommon.LabelKeyClusterCredentialType])
+			},
+		},
+		{
+			name: "Get error from createOrUpdate",
+			cfg: func() *KubeClusterConfig {
+				cfg := makeBaseClusterConfig("c-get-err")
+				cfg.AuthInfo.Token = "tok"
+				cfg.ClusterAlreadyExistCallback = func(string) bool { return true }
+				return cfg
+			}(),
+			cli: func() client.Client {
+				clusterName := "c-get-err"
+				pre := &corev1.Secret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:            clusterName,
+						Namespace:       ClusterGatewaySecretNamespace,
+						Labels:          map[string]string{clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeServiceAccountToken)},
+						ResourceVersion: "1",
+					},
+				}
+				base := fake.NewClientBuilder().WithScheme(scheme).WithObjects(pre).Build()
+				return &getErrorClient{Client: base, name: clusterName, namespace: ClusterGatewaySecretNamespace}
+			}(),
+			expectErr: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := tc.cfg.RegisterByVelaSecret(ctx, tc.cli)
+			if tc.expectErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+			if tc.verify != nil {
+				tc.verify(t, tc.cli, tc.cfg)
+			}
+		})
+	}
+}
+
+func TestLoadKubeClusterConfigFromFile(t *testing.T) {
+	testCases := []struct {
+		name      string
+		content   string
+		expectErr bool
+		verify    func(t *testing.T, cfg *KubeClusterConfig)
+	}{
+		{
+			name: "Valid kubeconfig",
+			content: `
+apiVersion: v1
+clusters:
+- cluster:
+    server: https://example.com
+  name: test-cluster
+contexts:
+- context:
+    cluster: test-cluster
+    user: test-user
+  name: test-context
+current-context: test-context
+kind: Config
+users:
+- name: test-user
+  user:
+    token: test-token
+`,
+			verify: func(t *testing.T, cfg *KubeClusterConfig) {
+				require.NotNil(t, cfg)
+				require.Equal(t, "test-cluster", cfg.ClusterName)
+				require.Equal(t, "https://example.com:443", cfg.Cluster.Server)
+				require.Equal(t, "test-token", cfg.AuthInfo.Token)
+			},
+		},
+		{
+			name:      "File does not exist",
+			content:   "",
+			expectErr: true,
+		},
+		{
+			name:      "Invalid kubeconfig",
+			content:   "invalid-yaml",
+			expectErr: true,
+		},
+		{
+			name: "No current context",
+			content: `
+apiVersion: v1
+clusters:
+- cluster:
+    server: https://example.com
+  name: test-cluster
+`,
+			expectErr: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			var path string
+			if tc.content != "" {
+				tmpfile, err := os.CreateTemp("", "kubeconfig")
+				require.NoError(t, err)
+				defer os.Remove(tmpfile.Name())
+				_, err = tmpfile.Write([]byte(tc.content))
+				require.NoError(t, err)
+				err = tmpfile.Close()
+				require.NoError(t, err)
+				path = tmpfile.Name()
+			} else {
+				path = "/non-existent-file"
+			}
+
+			cfg, err := LoadKubeClusterConfigFromFile(path)
+			if tc.expectErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+			if tc.verify != nil {
+				tc.verify(t, cfg)
+			}
+		})
+	}
+}
+
+func TestDetachCluster(t *testing.T) {
+	ctx := context.Background()
+	scheme := newTestScheme()
+	ClusterGatewaySecretNamespace = "vela-system"
+
+	testCases := []struct {
+		name        string
+		clusterName string
+		options     []DetachClusterOption
+		cli         client.Client
+		wantErr     bool
+		wantErrMsg  string
+	}{
+		{
+			name: "removeClusterFromResourceTrackers returns error",
+			cli: &mockClient{
+				Client:  fake.NewClientBuilder().WithScheme(scheme).Build(),
+				listErr: errors.New("list error"),
+			},
+			clusterName: "any-cluster",
+			wantErr:     true,
+			wantErrMsg:  "list error",
+		},
+		{
+			name:        "Detach local returns ErrReservedLocalClusterName",
+			cli:         fake.NewClientBuilder().WithScheme(scheme).Build(),
+			clusterName: ClusterLocalName,
+			wantErr:     true,
+			wantErrMsg:  ErrReservedLocalClusterName.Error(),
+		},
+		{
+			name: "OCM Loading kubeconfig fails",
+			cli: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.Secret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "ocm-load-cfg-fail",
+					Namespace: ClusterGatewaySecretNamespace,
+					Labels:    map[string]string{clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeOCMManagedCluster)},
+				},
+			}).Build(),
+			clusterName: "ocm-load-cfg-fail",
+			options:     []DetachClusterOption{DetachClusterManagedClusterKubeConfigPathOption("non-existent-path")},
+			wantErr:     true,
+		},
+		{
+			name: "OCM BuildConfig fails",
+			cli: func() client.Client {
+				tmpfile, err := os.CreateTemp("", "kubeconfig")
+				require.NoError(t, err)
+				defer os.Remove(tmpfile.Name())
+				_, err = tmpfile.Write([]byte("invalid kubeconfig"))
+				require.NoError(t, err)
+				err = tmpfile.Close()
+				require.NoError(t, err)
+				return fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.Secret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "ocm-build-cfg-fail",
+						Namespace: ClusterGatewaySecretNamespace,
+						Labels:    map[string]string{clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeOCMManagedCluster)},
+					},
+				}).Build()
+			}(),
+			clusterName: "ocm-build-cfg-fail",
+			options: func() []DetachClusterOption {
+				tmpfile, err := os.CreateTemp("", "kubeconfig")
+				require.NoError(t, err)
+				t.Cleanup(func() { os.Remove(tmpfile.Name()) })
+				_, err = tmpfile.Write([]byte("invalid kubeconfig"))
+				require.NoError(t, err)
+				err = tmpfile.Close()
+				require.NoError(t, err)
+				return []DetachClusterOption{DetachClusterManagedClusterKubeConfigPathOption(tmpfile.Name())}
+			}(),
+			wantErr: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := DetachCluster(ctx, tc.cli, tc.clusterName, tc.options...)
+			if tc.wantErr {
+				require.Error(t, err)
+				if tc.wantErrMsg != "" {
+					require.Contains(t, err.Error(), tc.wantErrMsg)
+				}
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRenameCluster(t *testing.T) {
+	ctx := context.Background()
+	scheme := newTestScheme()
+	ClusterGatewaySecretNamespace = "vela-system"
+
+	testCases := []struct {
+		name           string
+		oldClusterName string
+		newClusterName string
+		cli            client.Client
+		wantErr        bool
+		wantErrMsg     string
+		postCheck      func(t *testing.T, cli client.Client)
+	}{
+		{
+			name:           "New name is local: returns ErrReservedLocalClusterName",
+			oldClusterName: "old-cluster",
+			newClusterName: ClusterLocalName,
+			cli:            fake.NewClientBuilder().WithScheme(scheme).Build(),
+			wantErr:        true,
+			wantErrMsg:     ErrReservedLocalClusterName.Error(),
+		},
+		{
+			name:           "getMutableClusterSecret error: wraps with 'is not mutable now'",
+			oldClusterName: "non-existent-cluster",
+			newClusterName: "new-cluster",
+			cli:            fake.NewClientBuilder().WithScheme(scheme).Build(),
+			wantErr:        true,
+			wantErrMsg:     "is not mutable now",
+		},
+		{
+			name:           "ensureClusterNotExists returns ErrClusterExists: error returned",
+			oldClusterName: "old-cluster",
+			newClusterName: "existing-cluster",
+			cli: fake.NewClientBuilder().WithScheme(scheme).WithObjects(
+				&corev1.Secret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "old-cluster",
+						Namespace: ClusterGatewaySecretNamespace,
+						Labels:    map[string]string{clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeX509Certificate)},
+					},
+				},
+				&corev1.Secret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "existing-cluster",
+						Namespace: ClusterGatewaySecretNamespace,
+						Labels:    map[string]string{clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeX509Certificate)},
+					},
+					Data: map[string][]byte{"endpoint": []byte("https://example.com")},
+				},
+			).Build(),
+			wantErr:    true,
+			wantErrMsg: ErrClusterExists.Error(),
+		},
+		{
+			name:           "Delete old secret fails: error",
+			oldClusterName: "old-cluster",
+			newClusterName: "new-cluster",
+			cli: &mockClient{
+				Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.Secret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "old-cluster",
+						Namespace: ClusterGatewaySecretNamespace,
+						Labels:    map[string]string{clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeX509Certificate)},
+					},
+				}).Build(),
+				deleteErr: errors.New("delete failed"),
+			},
+			wantErr:    true,
+			wantErrMsg: "delete failed",
+		},
+		{
+			name:           "Create new secret fails: error",
+			oldClusterName: "old-cluster",
+			newClusterName: "new-cluster",
+			cli: &mockClient{
+				Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.Secret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "old-cluster",
+						Namespace: ClusterGatewaySecretNamespace,
+						Labels:    map[string]string{clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeX509Certificate)},
+					},
+				}).Build(),
+				createErr: errors.New("create failed"),
+			},
+			wantErr:    true,
+			wantErrMsg: "create failed",
+		},
+		{
+			name:           "Success: Old deleted, new created with same labels/annotations",
+			oldClusterName: "old-cluster",
+			newClusterName: "new-cluster",
+			cli: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.Secret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:        "old-cluster",
+					Namespace:   ClusterGatewaySecretNamespace,
+					Labels:      map[string]string{clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeX509Certificate), "label-key": "label-value"},
+					Annotations: map[string]string{"anno-key": "anno-value"},
+				},
+				Data: map[string][]byte{"key": []byte("value")},
+			}).Build(),
+			postCheck: func(t *testing.T, cli client.Client) {
+				err := cli.Get(ctx, client.ObjectKey{Name: "old-cluster", Namespace: ClusterGatewaySecretNamespace}, &corev1.Secret{})
+				require.True(t, apierrors.IsNotFound(err))
+				newSecret := &corev1.Secret{}
+				err = cli.Get(ctx, client.ObjectKey{Name: "new-cluster", Namespace: ClusterGatewaySecretNamespace}, newSecret)
+				require.NoError(t, err)
+				require.Equal(t, "new-cluster", newSecret.Name)
+				require.Equal(t, map[string]string{clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeX509Certificate), "label-key": "label-value"}, newSecret.Labels)
+				require.Equal(t, map[string]string{"anno-key": "anno-value"}, newSecret.Annotations)
+				require.Equal(t, map[string][]byte{"key": []byte("value")}, newSecret.Data)
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := RenameCluster(ctx, tc.cli, tc.oldClusterName, tc.newClusterName)
+			if tc.wantErr {
+				require.Error(t, err)
+				if tc.wantErrMsg != "" {
+					require.Contains(t, err.Error(), tc.wantErrMsg)
+				}
+			} else {
+				require.NoError(t, err)
+			}
+			if tc.postCheck != nil {
+				tc.postCheck(t, tc.cli)
+			}
+		})
+	}
+}
+
+// mock client to inject Get error on second secret fetch (createOrUpdate path)
+type getErrorClient struct {
+	client.Client
+	name      string
+	namespace string
+	count     int
+}
+
+func (g *getErrorClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
+	if key.Name == g.name && key.Namespace == g.namespace {
+		g.count++
+		if g.count >= 2 { // first call used by existence check, second by createOrUpdate
+			return errors.New("injected get error")
+		}
+	}
+	return g.Client.Get(ctx, key, obj, opts...)
+}
+
+func TestEnsureClusterNotExists(t *testing.T) {
+	ctx := context.Background()
+	scheme := newTestScheme()
+
+	testCases := []struct {
+		name      string
+		cli       client.Client
+		cluster   string
+		expectErr bool
+	}{
+		{
+			name:      "Cluster does not exist",
+			cli:       fake.NewClientBuilder().WithScheme(scheme).Build(),
+			cluster:   "non-existent",
+			expectErr: false,
+		},
+		{
+			name: "Cluster exists",
+			cli: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.Secret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "existing-cluster",
+					Namespace: ClusterGatewaySecretNamespace,
+					Labels:    map[string]string{clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeX509Certificate)},
+				},
+				Data: map[string][]byte{"endpoint": []byte("https://example.com")},
+			}).Build(),
+			cluster:   "existing-cluster",
+			expectErr: true,
+		},
+		{
+			name: "Client error",
+			cli: &mockClient{
+				Client: fake.NewClientBuilder().WithScheme(scheme).Build(),
+				getErr: errors.New("client error"),
+			},
+			cluster:   "any-cluster",
+			expectErr: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := ensureClusterNotExists(ctx, tc.cli, tc.cluster)
+			if tc.expectErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestEnsureNamespaceExists(t *testing.T) {
+	ctx := context.Background()
+	scheme := newTestScheme()
+
+	testCases := []struct {
+		name      string
+		cli       client.Client
+		cluster   string
+		namespace string
+		expectErr bool
+	}{
+		{
+			name: "Namespace already exists",
+			cli: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.Namespace{
+				ObjectMeta: metav1.ObjectMeta{Name: "existing-ns"},
+			}).Build(),
+			cluster:   "any-cluster",
+			namespace: "existing-ns",
+			expectErr: false,
+		},
+		{
+			name:      "Namespace does not exist",
+			cli:       fake.NewClientBuilder().WithScheme(scheme).Build(),
+			cluster:   "any-cluster",
+			namespace: "new-ns",
+			expectErr: false,
+		},
+		{
+			name: "Client Get error",
+			cli: &mockClient{
+				Client: fake.NewClientBuilder().WithScheme(scheme).Build(),
+				getErr: errors.New("client error"),
+			},
+			cluster:   "any-cluster",
+			namespace: "any-ns",
+			expectErr: true,
+		},
+		{
+			name: "Client Create error",
+			cli: &mockClient{
+				Client:    fake.NewClientBuilder().WithScheme(scheme).Build(),
+				createErr: errors.New("client error"),
+			},
+			cluster:   "any-cluster",
+			namespace: "new-ns",
+			expectErr: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := ensureNamespaceExists(ctx, tc.cli, tc.cluster, tc.namespace)
+			if tc.expectErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestGetMutableClusterSecret(t *testing.T) {
+	ctx := context.Background()
+	scheme := newTestScheme()
+	ClusterGatewaySecretNamespace = "vela-system"
+
+	testCases := []struct {
+		name      string
+		cli       client.Client
+		cluster   string
+		expectErr bool
+	}{
+		{
+			name:      "Secret does not exist",
+			cli:       fake.NewClientBuilder().WithScheme(scheme).Build(),
+			cluster:   "non-existent",
+			expectErr: true,
+		},
+		{
+			name: "Secret exists but no credential type label",
+			cli: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.Secret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "no-label",
+					Namespace: ClusterGatewaySecretNamespace,
+				},
+			}).Build(),
+			cluster:   "no-label",
+			expectErr: true,
+		},
+		{
+			name: "Secret exists with credential type label",
+			cli: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.Secret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "with-label",
+					Namespace: ClusterGatewaySecretNamespace,
+					Labels:    map[string]string{clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeX509Certificate)},
+				},
+			}).Build(),
+			cluster:   "with-label",
+			expectErr: false,
+		},
+		{
+			name: "Client Get error",
+			cli: &mockClient{
+				Client: fake.NewClientBuilder().WithScheme(scheme).Build(),
+				getErr: errors.New("client error"),
+			},
+			cluster:   "any-cluster",
+			expectErr: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := getMutableClusterSecret(ctx, tc.cli, tc.cluster)
+			if tc.expectErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRemoveClusterFromResourceTrackers(t *testing.T) {
+	ctx := context.Background()
+	scheme := newTestScheme()
+
+	testCases := []struct {
+		name      string
+		cli       client.Client
+		cluster   string
+		expectErr bool
+		verify    func(t *testing.T, cli client.Client)
+	}{
+		{
+			name:      "No resource trackers",
+			cli:       fake.NewClientBuilder().WithScheme(scheme).Build(),
+			cluster:   "any-cluster",
+			expectErr: false,
+		},
+		{
+			name: "Resource trackers exist, but none reference the cluster",
+			cli: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&v1beta1.ResourceTracker{
+				ObjectMeta: metav1.ObjectMeta{Name: "rt-1"},
+				Spec: v1beta1.ResourceTrackerSpec{
+					ManagedResources: []v1beta1.ManagedResource{
+						{ClusterObjectReference: common.ClusterObjectReference{Cluster: "other-cluster"}},
+					},
+				},
+			}).Build(),
+			cluster:   "any-cluster",
+			expectErr: false,
+		},
+		{
+			name: "Resource trackers exist and some reference the cluster",
+			cli: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&v1beta1.ResourceTracker{
+				ObjectMeta: metav1.ObjectMeta{Name: "rt-1"},
+				Spec: v1beta1.ResourceTrackerSpec{
+					ManagedResources: []v1beta1.ManagedResource{
+						{ClusterObjectReference: common.ClusterObjectReference{Cluster: "cluster-to-remove"}},
+						{ClusterObjectReference: common.ClusterObjectReference{Cluster: "other-cluster"}},
+					},
+				},
+			}).Build(),
+			cluster: "cluster-to-remove",
+			verify: func(t *testing.T, cli client.Client) {
+				var rt v1beta1.ResourceTracker
+				require.NoError(t, cli.Get(ctx, client.ObjectKey{Name: "rt-1"}, &rt))
+				require.Len(t, rt.Spec.ManagedResources, 1)
+				require.Equal(t, "other-cluster", rt.Spec.ManagedResources[0].Cluster)
+			},
+		},
+		{
+			name: "Client List error",
+			cli: &mockClient{
+				Client:  fake.NewClientBuilder().WithScheme(scheme).Build(),
+				listErr: errors.New("client error"),
+			},
+			cluster:   "any-cluster",
+			expectErr: true,
+		},
+		{
+			name: "Client Update error",
+			cli: &mockClient{
+				Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&v1beta1.ResourceTracker{
+					ObjectMeta: metav1.ObjectMeta{Name: "rt-1"},
+					Spec: v1beta1.ResourceTrackerSpec{
+						ManagedResources: []v1beta1.ManagedResource{
+							{ClusterObjectReference: common.ClusterObjectReference{Cluster: "cluster-to-remove"}},
+						},
+					},
+				}).Build(),
+				updateErr: errors.New("client error"),
+			},
+			cluster:   "cluster-to-remove",
+			expectErr: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := removeClusterFromResourceTrackers(ctx, tc.cli, tc.cluster)
+			if tc.expectErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+			if tc.verify != nil {
+				tc.verify(t, tc.cli)
+			}
+		})
+	}
+}
+
+func TestGetTokenFromExec(t *testing.T) {
+	testCases := []struct {
+		name      string
+		execCfg   *clientcmdapi.ExecConfig
+		setup     func(t *testing.T, cfg *clientcmdapi.ExecConfig)
+		expectErr bool
+	}{
+		{
+			name:    "Valid exec config",
+			execCfg: &clientcmdapi.ExecConfig{},
+			setup: func(t *testing.T, cfg *clientcmdapi.ExecConfig) {
+				dir := t.TempDir()
+				script := filepath.Join(dir, "test.sh")
+				require.NoError(t, os.WriteFile(script, []byte("#!/bin/sh\necho '{\"status\":{\"token\":\"test-token\"}}'"), 0755))
+				cfg.Command = script
+			},
+		},
+		{
+			name:      "Exec command fails",
+			execCfg:   &clientcmdapi.ExecConfig{Command: "/bin/false"},
+			expectErr: true,
+		},
+		{
+			name:    "Invalid JSON output",
+			execCfg: &clientcmdapi.ExecConfig{},
+			setup: func(t *testing.T, cfg *clientcmdapi.ExecConfig) {
+				dir := t.TempDir()
+				script := filepath.Join(dir, "test.sh")
+				require.NoError(t, os.WriteFile(script, []byte("#!/bin/sh\necho 'invalid-json'"), 0755))
+				cfg.Command = script
+			},
+			expectErr: true,
+		},
+		{
+			name:    "No token in JSON output",
+			execCfg: &clientcmdapi.ExecConfig{},
+			setup: func(t *testing.T, cfg *clientcmdapi.ExecConfig) {
+				dir := t.TempDir()
+				script := filepath.Join(dir, "test.sh")
+				require.NoError(t, os.WriteFile(script, []byte("#!/bin/sh\necho '{\"status\":{}}'"), 0755))
+				cfg.Command = script
+			},
+			expectErr: true,
+		},
+		{
+			name:      "Command with invalid characters",
+			execCfg:   &clientcmdapi.ExecConfig{Command: "/bin/echo; ls"},
+			expectErr: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			if tc.setup != nil {
+				tc.setup(t, tc.execCfg)
+			}
+			_, err := getTokenFromExec(tc.execCfg)
+			if tc.expectErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestAliasCluster(t *testing.T) {
+	ctx := context.Background()
+	scheme := newTestScheme()
+	ClusterGatewaySecretNamespace = "vela-system"
+
+	// The secret that will be used in some test cases
+	clusterSecret := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-cluster",
+			Namespace: ClusterGatewaySecretNamespace,
+			Labels:    map[string]string{clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeX509Certificate)},
+		},
+		Data: map[string][]byte{
+			"endpoint": []byte("https://example.com"),
+		},
+	}
+
+	testCases := []struct {
+		name        string
+		clusterName string
+		aliasName   string
+		cli         client.Client
+		wantErr     error  // for specific error types
+		wantErrMsg  string // for substring match
+		postCheck   func(t *testing.T, cli client.Client)
+	}{
+		{
+			name:        "Successfully alias cluster",
+			clusterName: "test-cluster",
+			aliasName:   "my-alias",
+			cli:         fake.NewClientBuilder().WithScheme(scheme).WithObjects(clusterSecret.DeepCopy()).Build(),
+			postCheck: func(t *testing.T, cli client.Client) {
+				updatedSecret := &corev1.Secret{}
+				err := cli.Get(ctx, client.ObjectKey{Name: "test-cluster", Namespace: ClusterGatewaySecretNamespace}, updatedSecret)
+				require.NoError(t, err)
+				annotations := updatedSecret.GetAnnotations()
+				require.NotNil(t, annotations)
+				require.Equal(t, "my-alias", annotations[clusterv1alpha1.AnnotationClusterAlias])
+			},
+		},
+		{
+			name:        "Local cluster returns error",
+			clusterName: ClusterLocalName,
+			aliasName:   "some-alias",
+			cli:         fake.NewClientBuilder().WithScheme(scheme).Build(),
+			wantErr:     ErrReservedLocalClusterName,
+		},
+		{
+			name:        "Cluster not found error",
+			clusterName: "non-existent-cluster",
+			aliasName:   "my-alias",
+			cli:         fake.NewClientBuilder().WithScheme(scheme).Build(),
+			wantErrMsg:  "no such cluster",
+		},
+		{
+			name:        "GetVirtualCluster fails",
+			clusterName: "test-cluster",
+			aliasName:   "my-alias",
+			cli: &mockClient{
+				Client: fake.NewClientBuilder().WithScheme(scheme).Build(),
+				getErr: errors.New("get error"),
+			},
+			wantErrMsg: "get error",
+		},
+		{
+			name:        "Client update fails",
+			clusterName: "test-cluster",
+			aliasName:   "my-alias",
+			cli: &mockClient{
+				Client:    fake.NewClientBuilder().WithScheme(scheme).WithObjects(clusterSecret.DeepCopy()).Build(),
+				updateErr: errors.New("update failed"),
+			},
+			wantErrMsg: "update failed",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := AliasCluster(ctx, tc.cli, tc.clusterName, tc.aliasName)
+
+			if tc.wantErr != nil {
+				require.Equal(t, tc.wantErr, err)
+			} else if tc.wantErrMsg != "" {
+				require.Error(t, err)
+				require.Contains(t, err.Error(), tc.wantErrMsg)
+			} else {
+				require.NoError(t, err)
+			}
+
+			if tc.postCheck != nil {
+				tc.postCheck(t, tc.cli)
+			}
+		})
+	}
+}
diff --git a/pkg/multicluster/utils_test.go b/pkg/multicluster/utils_test.go
index 0d0e4759..f0ba9046 100644
--- a/pkg/multicluster/utils_test.go
+++ b/pkg/multicluster/utils_test.go
@@ -22,12 +22,18 @@ import (
 
 	"github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
 	clustercommon "github.com/oam-dev/cluster-gateway/pkg/common"
-	v1 "k8s.io/api/core/v1"
-	v12 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"github.com/pkg/errors"
+	"github.com/stretchr/testify/require"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 
-	"github.com/oam-dev/kubevela/pkg/utils/common"
+	"github.com/oam-dev/kubevela/pkg/oam"
+
+	velacommon "github.com/oam-dev/kubevela/pkg/utils/common"
 )
 
 func TestUpgradeExistingClusterSecret(t *testing.T) {
@@ -37,28 +43,262 @@ func TestUpgradeExistingClusterSecret(t *testing.T) {
 		ClusterGatewaySecretNamespace = oldClusterGatewaySecretNamespace
 	}()
 	ctx := context.Background()
-	c := fake.NewClientBuilder().WithScheme(common.Scheme).Build()
-	secret := &v1.Secret{
-		ObjectMeta: v12.ObjectMeta{
+	c := fake.NewClientBuilder().WithScheme(velacommon.Scheme).Build()
+	secret := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
 			Name:      "example-outdated-cluster-secret",
 			Namespace: "default",
 			Labels: map[string]string{
 				"cluster.core.oam.dev/cluster-credential": "tls",
 			},
 		},
-		Type: v1.SecretTypeTLS,
+		Type: corev1.SecretTypeTLS,
 	}
-	if err := c.Create(ctx, secret); err != nil {
-		t.Fatalf("failed to create fake outdated cluster secret, err: %v", err)
+	require.NoError(t, c.Create(ctx, secret))
+	require.NoError(t, UpgradeExistingClusterSecret(ctx, c))
+	newSecret := &corev1.Secret{}
+	require.NoError(t, c.Get(ctx, client.ObjectKeyFromObject(secret), newSecret))
+	require.Equal(t, string(v1alpha1.CredentialTypeX509Certificate), newSecret.Labels[clustercommon.LabelKeyClusterCredentialType])
+}
+
+func TestContext(t *testing.T) {
+	t.Run("TestClusterNameInContext", func(t *testing.T) {
+		ctx := context.Background()
+		require.Equal(t, "", ClusterNameInContext(ctx))
+		ctx = ContextWithClusterName(ctx, "my-cluster")
+		require.Equal(t, "my-cluster", ClusterNameInContext(ctx))
+	})
+
+	t.Run("TestContextInLocalCluster", func(t *testing.T) {
+		ctx := context.Background()
+		ctx = ContextInLocalCluster(ctx)
+		require.Equal(t, ClusterLocalName, ClusterNameInContext(ctx))
+	})
+}
+
+func TestResourcesWithClusterName(t *testing.T) {
+	testCases := []struct {
+		name        string
+		clusterName string
+		objs        []*unstructured.Unstructured
+		expected    []*unstructured.Unstructured
+	}{
+		{
+			name:        "Empty slice",
+			clusterName: "my-cluster",
+			objs:        []*unstructured.Unstructured{},
+			expected:    nil,
+		},
+		{
+			name:        "Nil object",
+			clusterName: "my-cluster",
+			objs:        []*unstructured.Unstructured{nil},
+			expected:    nil,
+		},
+		{
+			name:        "Object without cluster name label",
+			clusterName: "my-cluster",
+			objs:        []*unstructured.Unstructured{{Object: map[string]interface{}{}}},
+			expected: []*unstructured.Unstructured{{
+				Object: map[string]interface{}{
+					"metadata": map[string]interface{}{
+						"labels": map[string]interface{}{
+							oam.LabelAppCluster: "my-cluster",
+						},
+					},
+				},
+			}},
+		},
+		{
+			name:        "Object with existing cluster name label",
+			clusterName: "my-cluster",
+			objs: []*unstructured.Unstructured{{
+				Object: map[string]interface{}{
+					"metadata": map[string]interface{}{
+						"labels": map[string]interface{}{
+							oam.LabelAppCluster: "other-cluster",
+						},
+					},
+				},
+			}},
+			expected: []*unstructured.Unstructured{{
+				Object: map[string]interface{}{
+					"metadata": map[string]interface{}{
+						"labels": map[string]interface{}{
+							oam.LabelAppCluster: "other-cluster",
+						},
+					},
+				},
+			}},
+		},
 	}
-	if err := UpgradeExistingClusterSecret(ctx, c); err != nil {
-		t.Fatalf("expect no error while upgrading outdated cluster secret but encounter error: %v", err)
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			result := ResourcesWithClusterName(tc.clusterName, tc.objs...)
+			require.Equal(t, tc.expected, result)
+		})
 	}
-	newSecret := &v1.Secret{}
-	if err := c.Get(ctx, client.ObjectKeyFromObject(secret), newSecret); err != nil {
-		t.Fatalf("found error while getting updated cluster secret: %v", err)
+}
+
+func TestGetClusterGatewayService(t *testing.T) {
+	ctx := context.Background()
+	scheme := newTestScheme()
+	apiregistrationv1.AddToScheme(scheme)
+
+	testCases := []struct {
+		name      string
+		cli       client.Client
+		expectErr bool
+		verify    func(t *testing.T, svc *apiregistrationv1.ServiceReference)
+	}{
+		{
+			name:      "APIService not found",
+			cli:       fake.NewClientBuilder().WithScheme(scheme).Build(),
+			expectErr: true,
+		},
+		{
+			name: "APIService found but no service spec",
+			cli: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&apiregistrationv1.APIService{
+				ObjectMeta: metav1.ObjectMeta{Name: "v1alpha1.cluster.core.oam.dev"},
+			}).Build(),
+			expectErr: true,
+		},
+		{
+			name: "APIService found but not available",
+			cli: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&apiregistrationv1.APIService{
+				ObjectMeta: metav1.ObjectMeta{Name: "v1alpha1.cluster.core.oam.dev"},
+				Spec: apiregistrationv1.APIServiceSpec{
+					Service: &apiregistrationv1.ServiceReference{
+						Name:      "my-service",
+						Namespace: "my-namespace",
+					},
+				},
+				Status: apiregistrationv1.APIServiceStatus{
+					Conditions: []apiregistrationv1.APIServiceCondition{
+						{
+							Type:   apiregistrationv1.Available,
+							Status: apiregistrationv1.ConditionFalse,
+						},
+					},
+				},
+			}).Build(),
+			expectErr: true,
+			verify: func(t *testing.T, svc *apiregistrationv1.ServiceReference) {
+				require.NotNil(t, svc)
+				require.Equal(t, "my-service", svc.Name)
+			},
+		},
+		{
+			name: "APIService found and available",
+			cli: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&apiregistrationv1.APIService{
+				ObjectMeta: metav1.ObjectMeta{Name: "v1alpha1.cluster.core.oam.dev"},
+				Spec: apiregistrationv1.APIServiceSpec{
+					Service: &apiregistrationv1.ServiceReference{
+						Name:      "my-service",
+						Namespace: "my-namespace",
+					},
+				},
+				Status: apiregistrationv1.APIServiceStatus{
+					Conditions: []apiregistrationv1.APIServiceCondition{
+						{
+							Type:   apiregistrationv1.Available,
+							Status: apiregistrationv1.ConditionTrue,
+						},
+					},
+				},
+			}).Build(),
+			verify: func(t *testing.T, svc *apiregistrationv1.ServiceReference) {
+				require.NotNil(t, svc)
+				require.Equal(t, "my-service", svc.Name)
+			},
+		},
+		{
+			name: "Client Get error",
+			cli: &mockClient{
+				Client: fake.NewClientBuilder().WithScheme(scheme).Build(),
+				getErr: errors.New("client error"),
+			},
+			expectErr: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			svc, err := GetClusterGatewayService(ctx, tc.cli)
+			if tc.expectErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+			if tc.verify != nil {
+				tc.verify(t, svc)
+			}
+		})
+	}
+}
+
+func TestListExistingClusterSecrets(t *testing.T) {
+	oldClusterGatewaySecretNamespace := ClusterGatewaySecretNamespace
+	defer func() {
+		ClusterGatewaySecretNamespace = oldClusterGatewaySecretNamespace
+	}()
+	ctx := context.Background()
+	scheme := newTestScheme()
+	ClusterGatewaySecretNamespace = "vela-system"
+
+	testCases := []struct {
+		name      string
+		cli       client.Client
+		expectErr bool
+		verify    func(t *testing.T, secrets []corev1.Secret)
+	}{
+		{
+			name: "No secrets exist",
+			cli:  fake.NewClientBuilder().WithScheme(scheme).Build(),
+			verify: func(t *testing.T, secrets []corev1.Secret) {
+				require.Empty(t, secrets)
+			},
+		},
+		{
+			name: "Secrets exist, but none have the required label",
+			cli: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.Secret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "no-label",
+					Namespace: ClusterGatewaySecretNamespace,
+				},
+			}).Build(),
+			verify: func(t *testing.T, secrets []corev1.Secret) {
+				require.Empty(t, secrets)
+			},
+		},
+		{
+			name: "Secrets exist with the required label",
+			cli: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.Secret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "with-label",
+					Namespace: ClusterGatewaySecretNamespace,
+					Labels:    map[string]string{clustercommon.LabelKeyClusterCredentialType: string(v1alpha1.CredentialTypeX509Certificate)},
+				},
+			}).Build(),
+			verify: func(t *testing.T, secrets []corev1.Secret) {
+				require.Len(t, secrets, 1)
+				require.Equal(t, "with-label", secrets[0].Name)
+			},
+		},
 	}
-	if newSecret.Labels[clustercommon.LabelKeyClusterCredentialType] != string(v1alpha1.CredentialTypeX509Certificate) {
-		t.Fatalf("updated secret label should has credential type x509")
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			secrets, err := ListExistingClusterSecrets(ctx, tc.cli)
+			if tc.expectErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+			if tc.verify != nil {
+				tc.verify(t, secrets)
+			}
+		})
 	}
 }
diff --git a/pkg/multicluster/virtual_cluster_test.go b/pkg/multicluster/virtual_cluster_test.go
index 7c936bdc..128edb59 100644
--- a/pkg/multicluster/virtual_cluster_test.go
+++ b/pkg/multicluster/virtual_cluster_test.go
@@ -18,6 +18,7 @@ package multicluster
 
 import (
 	"context"
+	"encoding/json"
 
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
@@ -155,6 +156,109 @@ var _ = Describe("Test Virtual Cluster", func() {
 		Expect(cv.Major).Should(BeEquivalentTo("1"))
 	})
 
+	It("Test virtual cluster helpers", func() {
+		By("Test FullName")
+		vcWithAlias := &VirtualCluster{Name: "test", Alias: "alias"}
+		Expect(vcWithAlias.FullName()).To(Equal("test (alias)"))
+		vcWithoutAlias := &VirtualCluster{Name: "test"}
+		Expect(vcWithoutAlias.FullName()).To(Equal("test"))
+
+		By("Test get/set cluster alias")
+		secret := &v1.Secret{}
+		setClusterAlias(secret, "my-alias")
+		Expect(getClusterAlias(secret)).To(Equal("my-alias"))
+		annots := secret.GetAnnotations()
+		Expect(annots).ToNot(BeNil())
+		Expect(annots[v1alpha1.AnnotationClusterAlias]).To(Equal("my-alias"))
+
+		By("Test NewVirtualClusterFromLocal")
+		vc := NewVirtualClusterFromLocal()
+		Expect(vc.Name).To(Equal(ClusterLocalName))
+		Expect(vc.Accepted).To(BeTrue())
+		Expect(vc.EndPoint).To(Equal(types.ClusterBlankEndpoint))
+
+		By("Test MatchVirtualClusterLabels")
+		ClusterGatewaySecretNamespace = "vela-system" // as set in other test
+		labels := MatchVirtualClusterLabels{"key": "val"}
+		opts := &client.ListOptions{}
+		labels.ApplyToList(opts)
+		Expect(opts.Namespace).To(Equal(ClusterGatewaySecretNamespace))
+		Expect(opts.LabelSelector).NotTo(BeNil())
+		Expect(opts.LabelSelector.String()).To(ContainSubstring("key=val"))
+		Expect(opts.LabelSelector.String()).To(ContainSubstring(clustercommon.LabelKeyClusterCredentialType))
+
+		delOpts := &client.DeleteAllOfOptions{}
+		labels.ApplyToDeleteAllOf(delOpts)
+		Expect(delOpts.ListOptions.Namespace).To(Equal(ClusterGatewaySecretNamespace))
+		Expect(delOpts.ListOptions.LabelSelector).NotTo(BeNil())
+		Expect(delOpts.ListOptions.LabelSelector.String()).To(ContainSubstring("key=val"))
+
+		By("Test get/set cluster version")
+		versionedSecret := &v1.Secret{}
+		cv := types.ClusterVersion{Major: "1", Minor: "20", GitVersion: "v1.20.0"}
+		setClusterVersion(versionedSecret, cv)
+
+		newCV, err := getClusterVersionFromObject(versionedSecret)
+		Expect(err).To(Succeed())
+		Expect(newCV).To(Equal(cv))
+
+		versionedSecret.Annotations = nil
+		_, err = getClusterVersionFromObject(versionedSecret)
+		Expect(err).ToNot(Succeed())
+
+		secretWithEmptyAnnotation := &v1.Secret{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}}}
+		_, err = getClusterVersionFromObject(secretWithEmptyAnnotation)
+		Expect(err).ToNot(Succeed())
+	})
+
+	It("Test GetVersionInfoFromObject", func() {
+		ClusterGatewaySecretNamespace = "vela-system3"
+		ctx := context.Background()
+		ns := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ClusterGatewaySecretNamespace}}
+		Expect(k8sClient.Create(ctx, ns)).Should(Succeed())
+		defer func() {
+			Expect(k8sClient.Delete(ctx, ns)).Should(Succeed())
+		}()
+
+		By("Setup a secret with version info")
+		cv := types.ClusterVersion{Major: "1", Minor: "21", GitVersion: "v1.21.0"}
+		cvJSON, err := json.Marshal(cv)
+		Expect(err).To(Succeed())
+		secret := &v1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "cluster-with-version",
+				Namespace: ClusterGatewaySecretNamespace,
+				Labels:    map[string]string{clustercommon.LabelKeyClusterCredentialType: "X509"},
+				Annotations: map[string]string{
+					types.AnnotationClusterVersion: string(cvJSON),
+				},
+			},
+		}
+		Expect(k8sClient.Create(ctx, secret)).Should(Succeed())
+
+		By("Test getting version from the secret")
+		retrievedCV := GetVersionInfoFromObject(ctx, k8sClient, "cluster-with-version")
+		Expect(retrievedCV).To(Equal(cv))
+
+		By("Test with a cluster that doesn't exist, should fallback to control plane version")
+		originalCPVersion := types.ControlPlaneClusterVersion
+		types.ControlPlaneClusterVersion = types.ClusterVersion{GitVersion: "v1.22.0"}
+		defer func() { types.ControlPlaneClusterVersion = originalCPVersion }()
+		retrievedCV = GetVersionInfoFromObject(ctx, k8sClient, "non-existent-cluster")
+		Expect(retrievedCV).To(Equal(types.ControlPlaneClusterVersion))
+
+		By("Test with a secret without version info, should fallback")
+		secretNoVersion := &v1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "cluster-no-version",
+				Namespace: ClusterGatewaySecretNamespace,
+				Labels:    map[string]string{clustercommon.LabelKeyClusterCredentialType: "X509"},
+			},
+		}
+		Expect(k8sClient.Create(ctx, secretNoVersion)).Should(Succeed())
+		retrievedCV = GetVersionInfoFromObject(ctx, k8sClient, "cluster-no-version")
+		Expect(retrievedCV).To(Equal(types.ControlPlaneClusterVersion))
+	})
 })
 
 type fakeClient struct {
diff --git a/pkg/webhook/core.oam.dev/v1beta1/application/validation_handlers_test.go b/pkg/webhook/core.oam.dev/v1beta1/application/validation_handlers_test.go
new file mode 100644
index 00000000..4b926aa7
--- /dev/null
+++ b/pkg/webhook/core.oam.dev/v1beta1/application/validation_handlers_test.go
@@ -0,0 +1,418 @@
+/*
+Copyright 2025 The KubeVela Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package application
+
+import (
+	"context"
+	"testing"
+
+	"github.com/kubevela/pkg/controller/sharding"
+	"github.com/stretchr/testify/assert"
+	admissionv1 "k8s.io/api/admission/v1"
+	authenticationv1 "k8s.io/api/authentication/v1"
+	authv1 "k8s.io/api/authorization/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	utilfeature "k8s.io/apiserver/pkg/util/feature"
+	featuregatetesting "k8s.io/component-base/featuregate/testing"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
+
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
+	"github.com/oam-dev/kubevela/pkg/features"
+	"github.com/oam-dev/kubevela/pkg/oam"
+)
+
+func TestValidateCreate(t *testing.T) {
+	// Disable the definition validation feature for this test
+	featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultMutableFeatureGate, features.ValidateDefinitionPermissions, false)
+
+	// Enable sharding to skip component validation (since we don't have component definitions in tests)
+	oldSharding := sharding.EnableSharding
+	sharding.EnableSharding = true
+	defer func() { sharding.EnableSharding = oldSharding }()
+
+	scheme := runtime.NewScheme()
+	_ = v1beta1.AddToScheme(scheme)
+	_ = authv1.AddToScheme(scheme)
+
+	testCases := []struct {
+		name               string
+		app                *v1beta1.Application
+		expectedErrorCount int
+		expectedErrorField string
+	}{
+		{
+			name: "valid application",
+			app: &v1beta1.Application{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-app",
+					Namespace: "default",
+				},
+				Spec: v1beta1.ApplicationSpec{
+					Components: []common.ApplicationComponent{
+						{
+							Name: "comp1",
+							Type: "webservice",
+						},
+					},
+				},
+			},
+			expectedErrorCount: 0,
+		},
+		{
+			name: "application with both autoUpdate and publishVersion",
+			app: &v1beta1.Application{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-app",
+					Namespace: "default",
+					Annotations: map[string]string{
+						oam.AnnotationAutoUpdate:     "true",
+						oam.AnnotationPublishVersion: "v1.0.0",
+					},
+				},
+				Spec: v1beta1.ApplicationSpec{
+					Components: []common.ApplicationComponent{
+						{
+							Name: "comp1",
+							Type: "webservice",
+						},
+					},
+				},
+			},
+			expectedErrorCount: 1,
+			expectedErrorField: "metadata.annotations",
+		},
+		{
+			name: "application with duplicate workflow step names",
+			app: &v1beta1.Application{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-app",
+					Namespace: "default",
+				},
+				Spec: v1beta1.ApplicationSpec{
+					Components: []common.ApplicationComponent{
+						{
+							Name: "comp1",
+							Type: "webservice",
+						},
+					},
+					Workflow: &v1beta1.Workflow{
+						Steps: []workflowv1alpha1.WorkflowStep{
+							{
+								WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
+									Name: "step1",
+									Type: "deploy",
+								},
+							},
+							{
+								WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
+									Name: "step1", // Duplicate name
+									Type: "deploy",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedErrorCount: 1,
+			expectedErrorField: "spec.workflow.steps",
+		},
+		{
+			name: "application with invalid timeout",
+			app: &v1beta1.Application{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-app",
+					Namespace: "default",
+				},
+				Spec: v1beta1.ApplicationSpec{
+					Components: []common.ApplicationComponent{
+						{
+							Name: "comp1",
+							Type: "webservice",
+						},
+					},
+					Workflow: &v1beta1.Workflow{
+						Steps: []workflowv1alpha1.WorkflowStep{
+							{
+								WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
+									Name:    "step1",
+									Type:    "deploy",
+									Timeout: "invalid",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedErrorCount: 1,
+			expectedErrorField: "spec.workflow.steps.timeout",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			handler := &ValidatingHandler{
+				Client: fake.NewClientBuilder().WithScheme(scheme).Build(),
+			}
+
+			req := admission.Request{
+				AdmissionRequest: admissionv1.AdmissionRequest{
+					UserInfo: authenticationv1.UserInfo{
+						Username: "test-user",
+						Groups:   []string{"test-group"},
+					},
+				},
+			}
+
+			errs := handler.ValidateCreate(context.Background(), tc.app, req)
+			assert.Equal(t, tc.expectedErrorCount, len(errs),
+				"Expected %d errors, got %d: %v", tc.expectedErrorCount, len(errs), errs)
+
+			if tc.expectedErrorField != "" && len(errs) > 0 {
+				assert.Contains(t, errs[0].Field, tc.expectedErrorField,
+					"Expected error field to contain %s, got %s", tc.expectedErrorField, errs[0].Field)
+			}
+		})
+	}
+}
+
+func TestValidateUpdate(t *testing.T) {
+	// Disable the definition validation feature for this test
+	featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultMutableFeatureGate, features.ValidateDefinitionPermissions, false)
+
+	// Enable sharding to skip component validation (since we don't have component definitions in tests)
+	oldSharding := sharding.EnableSharding
+	sharding.EnableSharding = true
+	defer func() { sharding.EnableSharding = oldSharding }()
+
+	scheme := runtime.NewScheme()
+	_ = v1beta1.AddToScheme(scheme)
+	_ = authv1.AddToScheme(scheme)
+
+	handler := &ValidatingHandler{
+		Client: fake.NewClientBuilder().WithScheme(scheme).Build(),
+	}
+
+	oldApp := &v1beta1.Application{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-app",
+			Namespace: "default",
+		},
+		Spec: v1beta1.ApplicationSpec{
+			Components: []common.ApplicationComponent{
+				{
+					Name: "comp1",
+					Type: "webservice",
+				},
+			},
+		},
+	}
+
+	testCases := []struct {
+		name               string
+		newApp             *v1beta1.Application
+		expectedErrorCount int
+	}{
+		{
+			name: "valid update",
+			newApp: &v1beta1.Application{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-app",
+					Namespace: "default",
+				},
+				Spec: v1beta1.ApplicationSpec{
+					Components: []common.ApplicationComponent{
+						{
+							Name: "comp1",
+							Type: "webservice",
+						},
+						{
+							Name: "comp2",
+							Type: "worker",
+						},
+					},
+				},
+			},
+			expectedErrorCount: 0,
+		},
+		{
+			name: "update with invalid annotations",
+			newApp: &v1beta1.Application{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-app",
+					Namespace: "default",
+					Annotations: map[string]string{
+						oam.AnnotationAutoUpdate:     "true",
+						oam.AnnotationPublishVersion: "v1.0.0",
+					},
+				},
+				Spec: v1beta1.ApplicationSpec{
+					Components: []common.ApplicationComponent{
+						{
+							Name: "comp1",
+							Type: "webservice",
+						},
+					},
+				},
+			},
+			expectedErrorCount: 1,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			req := admission.Request{
+				AdmissionRequest: admissionv1.AdmissionRequest{
+					UserInfo: authenticationv1.UserInfo{
+						Username: "test-user",
+						Groups:   []string{"test-group"},
+					},
+				},
+			}
+
+			errs := handler.ValidateUpdate(context.Background(), tc.newApp, oldApp, req)
+			assert.Equal(t, tc.expectedErrorCount, len(errs),
+				"Expected %d errors, got %d: %v", tc.expectedErrorCount, len(errs), errs)
+		})
+	}
+}
+
+func TestValidateTimeout(t *testing.T) {
+	handler := &ValidatingHandler{}
+
+	testCases := []struct {
+		name          string
+		timeout       string
+		expectedError bool
+	}{
+		{
+			name:          "valid duration - seconds",
+			timeout:       "30s",
+			expectedError: false,
+		},
+		{
+			name:          "valid duration - minutes",
+			timeout:       "5m",
+			expectedError: false,
+		},
+		{
+			name:          "valid duration - hours",
+			timeout:       "2h",
+			expectedError: false,
+		},
+		{
+			name:          "valid duration - complex",
+			timeout:       "1h30m45s",
+			expectedError: false,
+		},
+		{
+			name:          "invalid duration",
+			timeout:       "invalid",
+			expectedError: true,
+		},
+		{
+			name:          "invalid duration - missing unit",
+			timeout:       "30",
+			expectedError: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			errs := handler.ValidateTimeout("test-step", tc.timeout)
+			if tc.expectedError {
+				assert.NotEmpty(t, errs, "Expected validation error for timeout %s", tc.timeout)
+			} else {
+				assert.Empty(t, errs, "Expected no validation error for timeout %s", tc.timeout)
+			}
+		})
+	}
+}
+
+func TestValidateAnnotations(t *testing.T) {
+	handler := &ValidatingHandler{}
+
+	testCases := []struct {
+		name               string
+		annotations        map[string]string
+		expectedErrorCount int
+	}{
+		{
+			name:               "no annotations",
+			annotations:        nil,
+			expectedErrorCount: 0,
+		},
+		{
+			name: "only autoUpdate",
+			annotations: map[string]string{
+				oam.AnnotationAutoUpdate: "true",
+			},
+			expectedErrorCount: 0,
+		},
+		{
+			name: "only publishVersion",
+			annotations: map[string]string{
+				oam.AnnotationPublishVersion: "v1.0.0",
+			},
+			expectedErrorCount: 0,
+		},
+		{
+			name: "both autoUpdate and publishVersion",
+			annotations: map[string]string{
+				oam.AnnotationAutoUpdate:     "true",
+				oam.AnnotationPublishVersion: "v1.0.0",
+			},
+			expectedErrorCount: 1,
+		},
+		{
+			name: "autoUpdate false with publishVersion",
+			annotations: map[string]string{
+				oam.AnnotationAutoUpdate:     "false",
+				oam.AnnotationPublishVersion: "v1.0.0",
+			},
+			expectedErrorCount: 0,
+		},
+		{
+			name: "autoUpdate true with empty publishVersion",
+			annotations: map[string]string{
+				oam.AnnotationAutoUpdate:     "true",
+				oam.AnnotationPublishVersion: "",
+			},
+			expectedErrorCount: 0,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			app := &v1beta1.Application{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:        "test-app",
+					Namespace:   "default",
+					Annotations: tc.annotations,
+				},
+			}
+
+			errs := handler.ValidateAnnotations(context.Background(), app)
+			assert.Equal(t, tc.expectedErrorCount, len(errs),
+				"Expected %d errors, got %d: %v", tc.expectedErrorCount, len(errs), errs)
+		})
+	}
+}
diff --git a/pkg/webhook/core.oam.dev/v1beta1/application/validation_permissions_test.go b/pkg/webhook/core.oam.dev/v1beta1/application/validation_permissions_test.go
index 88aa8ade..a50ca153 100644
--- a/pkg/webhook/core.oam.dev/v1beta1/application/validation_permissions_test.go
+++ b/pkg/webhook/core.oam.dev/v1beta1/application/validation_permissions_test.go
@@ -685,8 +685,21 @@ func TestValidateDefinitionPermissions_AuthenticationDisabled(t *testing.T) {
 		},
 	}
 
+	// Use mock client that allows the definition
+	scheme := runtime.NewScheme()
+	_ = v1beta1.AddToScheme(scheme)
+	_ = authv1.AddToScheme(scheme)
+
+	fakeClient := &mockSARClient{
+		Client: fake.NewClientBuilder().WithScheme(scheme).Build(),
+		allowedDefinitions: map[string]bool{
+			"componentdefinitions/vela-system/webservice": true,
+			"componentdefinitions/test-ns/webservice":     true,
+		},
+	}
+
 	handler := &ValidatingHandler{
-		Client: fake.NewClientBuilder().Build(),
+		Client: fakeClient,
 	}
 
 	req := admission.Request{
@@ -697,9 +710,10 @@ func TestValidateDefinitionPermissions_AuthenticationDisabled(t *testing.T) {
 		},
 	}
 
-	// Should return no errors when AuthenticationWithUser is disabled
+	// Definition validation should still work even when AuthenticationWithUser is disabled
+	// It validates based on the user info in the request regardless of auth flag
 	errs := handler.ValidateDefinitionPermissions(context.Background(), app, req)
-	assert.Empty(t, errs, "Expected no errors when AuthenticationWithUser is disabled")
+	assert.Empty(t, errs, "Expected no errors when user has permission even with AuthenticationWithUser disabled")
 }
 
 func TestCollectDefinitionUsage(t *testing.T) {
diff --git a/pkg/webhook/core.oam.dev/v1beta1/componentdefinition/validating_handler_test.go b/pkg/webhook/core.oam.dev/v1beta1/componentdefinition/component_definition_validating_handler_test.go
similarity index 64%
rename from pkg/webhook/core.oam.dev/v1beta1/componentdefinition/validating_handler_test.go
rename to pkg/webhook/core.oam.dev/v1beta1/componentdefinition/component_definition_validating_handler_test.go
index 3e1c9578..4e57758c 100644
--- a/pkg/webhook/core.oam.dev/v1beta1/componentdefinition/validating_handler_test.go
+++ b/pkg/webhook/core.oam.dev/v1beta1/componentdefinition/component_definition_validating_handler_test.go
@@ -30,6 +30,7 @@ import (
 	admissionv1 "k8s.io/api/admission/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
+	utilfeature "k8s.io/apiserver/pkg/util/feature"
 	"k8s.io/client-go/kubernetes/scheme"
 	"k8s.io/client-go/rest"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -39,6 +40,7 @@ import (
 	core "github.com/oam-dev/kubevela/apis/core.oam.dev"
 	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
+	"github.com/oam-dev/kubevela/pkg/features"
 )
 
 var handler ValidatingHandler
@@ -153,7 +155,7 @@ var _ = Describe("Test ComponentDefinition validating handler", func() {
 			resp := handler.Handle(context.TODO(), req)
 			Expect(resp.Allowed).Should(BeFalse())
 			Expect(resp.Result.Reason).Should(Equal(metav1.StatusReason(http.StatusText(http.StatusForbidden))))
-			Expect(resp.Result.Message).Should(Equal("neither the type nor the definition of the workload field in the ComponentDefinition wrongCd can be empty"))
+			Expect(resp.Result.Message).Should(ContainSubstring("neither the type nor the definition of the workload field in the ComponentDefinition wrongCd can be empty"))
 		})
 
 		It("Test componentDefinition which type and definition point to different workload type", func() {
@@ -176,7 +178,7 @@ var _ = Describe("Test ComponentDefinition validating handler", func() {
 			resp := handler.Handle(context.TODO(), req)
 			Expect(resp.Allowed).Should(BeFalse())
 			Expect(resp.Result.Reason).Should(Equal(metav1.StatusReason(http.StatusText(http.StatusForbidden))))
-			Expect(resp.Result.Message).Should(Equal("the type and the definition of the workload field in ComponentDefinition wrongCd should represent the same workload"))
+			Expect(resp.Result.Message).Should(ContainSubstring("the type and the definition of the workload field in ComponentDefinition wrongCd should represent the same workload"))
 		})
 		It("Test cue template validation passed", func() {
 			cd.Spec = v1beta1.ComponentDefinitionSpec{
@@ -401,5 +403,278 @@ var _ = Describe("Test ComponentDefinition validating handler", func() {
 			Expect(resp.Allowed).Should(BeTrue())
 		})
 
+		It("Test ComponentDefinition with non-existent CRD in outputs should be rejected", func() {
+			// Enable the ValidateResourcesExist feature gate for this test
+			originalState := utilfeature.DefaultMutableFeatureGate.Enabled(features.ValidateResourcesExist)
+			defer utilfeature.DefaultMutableFeatureGate.SetFromMap(map[string]bool{
+				string(features.ValidateResourcesExist): originalState,
+			})
+			utilfeature.DefaultMutableFeatureGate.SetFromMap(map[string]bool{
+				string(features.ValidateResourcesExist): true,
+			})
+
+			templateWithInvalidCRD := `
+parameter: {
+	name: string
+	image: string
+}
+
+output: {
+	apiVersion: "apps/v1"
+	kind: "Deployment"
+	metadata: name: parameter.name
+	spec: {
+		selector: matchLabels: app: parameter.name
+		template: {
+			metadata: labels: app: parameter.name
+			spec: containers: [{
+				name: parameter.name
+				image: parameter.image
+			}]
+		}
+	}
+}
+
+outputs: {
+	invalidResource: {
+		apiVersion: "custom.io/v1alpha1"
+		kind: "NonExistentResource"
+		metadata: name: parameter.name + "-custom"
+		spec: {
+			foo: "bar"
+		}
+	}
+}`
+
+			cd := v1beta1.ComponentDefinition{}
+			cd.SetGroupVersionKind(v1beta1.ComponentDefinitionGroupVersionKind)
+			cd.SetName("test-invalid-outputs")
+			cd.Spec = v1beta1.ComponentDefinitionSpec{
+				Workload: common.WorkloadTypeDescriptor{
+					Type: "deployments.apps",
+					Definition: common.WorkloadGVK{
+						APIVersion: "apps/v1",
+						Kind:       "Deployment",
+					},
+				},
+				Schematic: &common.Schematic{
+					CUE: &common.CUE{
+						Template: templateWithInvalidCRD,
+					},
+				},
+			}
+			cdRaw, _ := json.Marshal(cd)
+			req := admission.Request{
+				AdmissionRequest: admissionv1.AdmissionRequest{
+					Operation: admissionv1.Create,
+					Resource:  reqResource,
+					Object:    runtime.RawExtension{Raw: cdRaw},
+				},
+			}
+			resp := handler.Handle(context.TODO(), req)
+			Expect(resp.Allowed).Should(BeFalse())
+			Expect(resp.Result.Message).Should(ContainSubstring("resource type not found on cluster"))
+		})
+
+		It("Test ComponentDefinition with valid outputs should pass", func() {
+			templateWithValidOutputs := `
+parameter: {
+	name: string
+	image: string
+}
+
+output: {
+	apiVersion: "apps/v1"
+	kind: "Deployment"
+	metadata: name: parameter.name
+	spec: {
+		selector: matchLabels: app: parameter.name
+		template: {
+			metadata: labels: app: parameter.name
+			spec: containers: [{
+				name: parameter.name
+				image: parameter.image
+			}]
+		}
+	}
+}
+
+outputs: {
+	service: {
+		apiVersion: "v1"
+		kind: "Service"
+		metadata: name: parameter.name + "-svc"
+		spec: {
+			selector: app: parameter.name
+			ports: [{
+				port: 80
+				targetPort: 8080
+			}]
+		}
+	}
+	configmap: {
+		apiVersion: "v1"
+		kind: "ConfigMap"
+		metadata: name: parameter.name + "-config"
+		data: {
+			config: "test-config"
+		}
+	}
+}`
+
+			cd := v1beta1.ComponentDefinition{}
+			cd.SetGroupVersionKind(v1beta1.ComponentDefinitionGroupVersionKind)
+			cd.SetName("test-valid-outputs")
+			cd.Spec = v1beta1.ComponentDefinitionSpec{
+				Workload: common.WorkloadTypeDescriptor{
+					Type: "deployments.apps",
+					Definition: common.WorkloadGVK{
+						APIVersion: "apps/v1",
+						Kind:       "Deployment",
+					},
+				},
+				Schematic: &common.Schematic{
+					CUE: &common.CUE{
+						Template: templateWithValidOutputs,
+					},
+				},
+			}
+			cdRaw, _ := json.Marshal(cd)
+			req := admission.Request{
+				AdmissionRequest: admissionv1.AdmissionRequest{
+					Operation: admissionv1.Create,
+					Resource:  reqResource,
+					Object:    runtime.RawExtension{Raw: cdRaw},
+				},
+			}
+			resp := handler.Handle(context.TODO(), req)
+			Expect(resp.Allowed).Should(BeTrue())
+		})
+
+		It("Test ComponentDefinition with mixed valid and non-k8s outputs should pass", func() {
+			templateWithMixedOutputs := `
+parameter: {
+	name: string
+	image: string
+}
+
+output: {
+	apiVersion: "apps/v1"
+	kind: "Deployment"
+	metadata: name: parameter.name
+	spec: {
+		selector: matchLabels: app: parameter.name
+		template: {
+			metadata: labels: app: parameter.name
+			spec: containers: [{
+				name: parameter.name
+				image: parameter.image
+			}]
+		}
+	}
+}
+
+outputs: {
+	service: {
+		apiVersion: "v1"
+		kind: "Service"
+		metadata: name: parameter.name + "-svc"
+		spec: {
+			selector: app: parameter.name
+			ports: [{port: 80}]
+		}
+	}
+	customData: {
+		field1: "value1"
+		field2: "value2"
+		nested: {
+			data: "some-data"
+		}
+	}
+}`
+
+			cd := v1beta1.ComponentDefinition{}
+			cd.SetGroupVersionKind(v1beta1.ComponentDefinitionGroupVersionKind)
+			cd.SetName("test-mixed-outputs")
+			cd.Spec = v1beta1.ComponentDefinitionSpec{
+				Workload: common.WorkloadTypeDescriptor{
+					Type: "deployments.apps",
+					Definition: common.WorkloadGVK{
+						APIVersion: "apps/v1",
+						Kind:       "Deployment",
+					},
+				},
+				Schematic: &common.Schematic{
+					CUE: &common.CUE{
+						Template: templateWithMixedOutputs,
+					},
+				},
+			}
+			cdRaw, _ := json.Marshal(cd)
+			req := admission.Request{
+				AdmissionRequest: admissionv1.AdmissionRequest{
+					Operation: admissionv1.Create,
+					Resource:  reqResource,
+					Object:    runtime.RawExtension{Raw: cdRaw},
+				},
+			}
+			resp := handler.Handle(context.TODO(), req)
+			Expect(resp.Allowed).Should(BeTrue())
+		})
+
+		It("Test ComponentDefinition with empty outputs should pass", func() {
+			templateWithEmptyOutputs := `
+parameter: {
+	name: string
+	image: string
+}
+
+output: {
+	apiVersion: "apps/v1"
+	kind: "Deployment"
+	metadata: name: parameter.name
+	spec: {
+		selector: matchLabels: app: parameter.name
+		template: {
+			metadata: labels: app: parameter.name
+			spec: containers: [{
+				name: parameter.name
+				image: parameter.image
+			}]
+		}
+	}
+}
+
+outputs: {}`
+
+			cd := v1beta1.ComponentDefinition{}
+			cd.SetGroupVersionKind(v1beta1.ComponentDefinitionGroupVersionKind)
+			cd.SetName("test-empty-outputs")
+			cd.Spec = v1beta1.ComponentDefinitionSpec{
+				Workload: common.WorkloadTypeDescriptor{
+					Type: "deployments.apps",
+					Definition: common.WorkloadGVK{
+						APIVersion: "apps/v1",
+						Kind:       "Deployment",
+					},
+				},
+				Schematic: &common.Schematic{
+					CUE: &common.CUE{
+						Template: templateWithEmptyOutputs,
+					},
+				},
+			}
+			cdRaw, _ := json.Marshal(cd)
+			req := admission.Request{
+				AdmissionRequest: admissionv1.AdmissionRequest{
+					Operation: admissionv1.Create,
+					Resource:  reqResource,
+					Object:    runtime.RawExtension{Raw: cdRaw},
+				},
+			}
+			resp := handler.Handle(context.TODO(), req)
+			Expect(resp.Allowed).Should(BeTrue())
+		})
+
 	})
 })
diff --git a/pkg/webhook/core.oam.dev/v1beta1/policydefinition/validating_handler_test.go b/pkg/webhook/core.oam.dev/v1beta1/policydefinition/policy_definition_validating_handler_test.go
similarity index 100%
rename from pkg/webhook/core.oam.dev/v1beta1/policydefinition/validating_handler_test.go
rename to pkg/webhook/core.oam.dev/v1beta1/policydefinition/policy_definition_validating_handler_test.go
diff --git a/pkg/webhook/core.oam.dev/v1beta1/traitdefinition/validating_handler_test.go b/pkg/webhook/core.oam.dev/v1beta1/traitdefinition/trait_definition_validating_handler_test.go
similarity index 99%
rename from pkg/webhook/core.oam.dev/v1beta1/traitdefinition/validating_handler_test.go
rename to pkg/webhook/core.oam.dev/v1beta1/traitdefinition/trait_definition_validating_handler_test.go
index 9fdf5899..ec8675e9 100644
--- a/pkg/webhook/core.oam.dev/v1beta1/traitdefinition/validating_handler_test.go
+++ b/pkg/webhook/core.oam.dev/v1beta1/traitdefinition/trait_definition_validating_handler_test.go
@@ -168,7 +168,7 @@ var _ = Describe("Test TraitDefinition validating handler", func() {
 			resp := handler.Handle(context.TODO(), req)
 			Expect(resp.Allowed).Should(BeFalse())
 			Expect(resp.Result.Reason).Should(Equal(metav1.StatusReason(http.StatusText(http.StatusForbidden))))
-			Expect(resp.Result.Message).Should(Equal("mock validator error"))
+			Expect(resp.Result.Message).Should(ContainSubstring("mock validator error"))
 		})
 		It("Test cue template validation passed", func() {
 			td.Spec = v1beta1.TraitDefinitionSpec{
diff --git a/pkg/webhook/core.oam.dev/v1beta1/workflowstepdefinition/validating_handler_test.go b/pkg/webhook/core.oam.dev/v1beta1/workflowstepdefinition/workflowstep_validating_handler_test.go
similarity index 100%
rename from pkg/webhook/core.oam.dev/v1beta1/workflowstepdefinition/validating_handler_test.go
rename to pkg/webhook/core.oam.dev/v1beta1/workflowstepdefinition/workflowstep_validating_handler_test.go
diff --git a/pkg/webhook/utils/invalid_resource_check_test.go b/pkg/webhook/utils/invalid_resource_check_test.go
new file mode 100644
index 00000000..c2f9d9bd
--- /dev/null
+++ b/pkg/webhook/utils/invalid_resource_check_test.go
@@ -0,0 +1,307 @@
+/*
+ Copyright 2021. The KubeVela Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package utils
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"k8s.io/apimachinery/pkg/api/meta"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	utilfeature "k8s.io/apiserver/pkg/util/feature"
+
+	"github.com/oam-dev/kubevela/pkg/features"
+)
+
+// fakeRESTMapper implements meta.RESTMapper for tests (minimal).
+type fakeRESTMapper struct {
+	known map[string]bool // key: group|kind|version
+}
+
+func newFakeRESTMapper(entries ...[3]string) *fakeRESTMapper {
+	m := &fakeRESTMapper{known: map[string]bool{}}
+	for _, e := range entries {
+		m.known[e[0]+"|"+e[1]+"|"+e[2]] = true
+	}
+	return m
+}
+
+func (f *fakeRESTMapper) key(gk schema.GroupKind, version string) string {
+	return gk.Group + "|" + gk.Kind + "|" + version
+}
+
+func (f *fakeRESTMapper) Reset() {}
+func (f *fakeRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
+	return schema.GroupVersionKind{}, errors.New("not implemented")
+}
+func (f *fakeRESTMapper) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
+	return nil, errors.New("not implemented")
+}
+func (f *fakeRESTMapper) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) {
+	return schema.GroupVersionResource{}, errors.New("not implemented")
+}
+func (f *fakeRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
+	return nil, errors.New("not implemented")
+}
+func (f *fakeRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) {
+	for _, v := range versions {
+		if f.known[f.key(gk, v)] {
+			return &meta.RESTMapping{
+				Resource: schema.GroupVersionResource{
+					Group:    gk.Group,
+					Version:  v,
+					Resource: "",
+				},
+				GroupVersionKind: schema.GroupVersionKind{
+					Group:   gk.Group,
+					Version: v,
+					Kind:    gk.Kind,
+				},
+			}, nil
+		}
+	}
+	return nil, errors.New("no match for kind")
+}
+func (f *fakeRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) {
+	m, err := f.RESTMapping(gk, versions...)
+	if err != nil {
+		return nil, err
+	}
+	return []*meta.RESTMapping{m}, nil
+}
+func (f *fakeRESTMapper) ResourceSingularizer(resource string) (string, error) {
+	return resource, nil
+}
+
+func TestExtractResourceInfo(t *testing.T) {
+	tests := map[string]struct {
+		cue     string
+		want    []ResourceInfo
+		wantErr bool
+	}{
+		"singleOutput": {
+			cue: `
+output: {
+  apiVersion: "apps/v1"
+  kind: "Deployment"
+  metadata: { name: "my-deploy" }
+  spec: {}
+}`,
+			want: []ResourceInfo{
+				{APIVersion: "apps/v1", Kind: "Deployment", Name: "my-deploy"},
+			},
+		},
+		"multipleOutputs": {
+			cue: `
+outputs: {
+  first: {
+    apiVersion: "v1"
+    kind: "ConfigMap"
+    metadata: { name: "cfg" }
+  }
+  second: {
+    apiVersion: "batch/v1"
+    kind: "Job"
+  }
+  third: { kind: "Service" } // missing apiVersion ignored
+}`,
+			want: []ResourceInfo{
+				{APIVersion: "v1", Kind: "ConfigMap", Name: "cfg"},
+				{APIVersion: "batch/v1", Kind: "Job", Name: "second"},
+			},
+		},
+		"parseError": {
+			cue: `
+output: {
+  apiVersion: "v1"
+  kind: "Pod"
+`,
+			wantErr: true,
+		},
+		"outputWithoutKindIgnored": {
+			cue: `
+output: {
+  apiVersion: "v1"
+  metadata: { name: "x" }
+}`,
+			want: []ResourceInfo{},
+		},
+		"metadataNameOverridesLabel": {
+			cue: `
+outputs: {
+  cfg: { apiVersion: "v1", kind: "ConfigMap", metadata: { name: "real" } }
+}`,
+			want: []ResourceInfo{
+				{APIVersion: "v1", Kind: "ConfigMap", Name: "real"},
+			},
+		},
+		"fallbackNameCoreGroup": {
+			cue: `
+outputs: {
+  svc: { apiVersion: "v1", kind: "Service" }
+}`,
+			want: []ResourceInfo{
+				{APIVersion: "v1", Kind: "Service", Name: "svc"},
+			},
+		},
+		"deepMetadataNameIgnoredFallbackToLabel": {
+			cue: `
+outputs: {
+  deep: { apiVersion: "v1", kind: "ConfigMap", metadata: { other: { name: "inner" } } }
+}`,
+			want: []ResourceInfo{
+				{APIVersion: "v1", Kind: "ConfigMap", Name: "deep"},
+			},
+		},
+		"nonStructOutputsValueIgnored": {
+			cue: `
+outputs: [
+  { apiVersion: "v1", kind: "ConfigMap" }
+]`,
+			want: []ResourceInfo{},
+		},
+		"missingAPIVersionAndKindEntriesIgnored": {
+			cue: `
+outputs: {
+  a: { apiVersion: "v1" }
+  b: { kind: "ConfigMap" }
+  c: { metadata: { name: "x" } }
+}`,
+			want: []ResourceInfo{},
+		},
+	}
+
+	for name, tc := range tests {
+		tc := tc
+		t.Run(name, func(t *testing.T) {
+			got, err := ExtractResourceInfo(tc.cue)
+			if tc.wantErr {
+				assert.Error(t, err)
+				return
+			}
+			assert.NoError(t, err)
+			if len(tc.want) == 0 {
+				assert.Len(t, got, 0)
+			} else {
+				assert.Equal(t, tc.want, got)
+			}
+		})
+	}
+}
+
+func TestValidateOutputResourcesExist(t *testing.T) {
+	// Enable the ValidateResourcesExist feature gate for these tests
+	originalState := utilfeature.DefaultMutableFeatureGate.Enabled(features.ValidateResourcesExist)
+	defer utilfeature.DefaultMutableFeatureGate.SetFromMap(map[string]bool{
+		string(features.ValidateResourcesExist): originalState,
+	})
+	utilfeature.DefaultMutableFeatureGate.SetFromMap(map[string]bool{
+		string(features.ValidateResourcesExist): true,
+	})
+
+	tests := map[string]struct {
+		cue     string
+		mapper  *fakeRESTMapper
+		wantErr string
+	}{
+		"singleSuccess": {
+			cue: `
+output: {
+  apiVersion: "apps/v1"
+  kind: "Deployment"
+  metadata: { name: "my-deploy" }
+}`,
+			mapper: newFakeRESTMapper([3]string{"apps", "Deployment", "v1"}),
+		},
+		"multipleSuccess": {
+			cue: `
+outputs: {
+  cm: { apiVersion: "v1", kind: "ConfigMap" }
+  jobRes: { apiVersion: "batch/v1", kind: "Job" }
+}`,
+			mapper: newFakeRESTMapper(
+				[3]string{"", "ConfigMap", "v1"},
+				[3]string{"batch", "Job", "v1"},
+			),
+		},
+		"missingResource": {
+			cue: `
+output: {
+  apiVersion: "apps/v1"
+  kind: "Deployment"
+}`,
+			mapper:  newFakeRESTMapper(),
+			wantErr: "resource type not found on cluster: apps/v1/Deployment",
+		},
+		"firstOkSecondMissing": {
+			cue: `
+outputs: {
+  a: { apiVersion: "v1", kind: "ConfigMap" }
+  b: { apiVersion: "batch/v1", kind: "Job" }
+}`,
+			mapper:  newFakeRESTMapper([3]string{"", "ConfigMap", "v1"}),
+			wantErr: "resource type not found on cluster: batch/v1/Job",
+		},
+		"ignoreIncompleteEntries": {
+			cue: `
+outputs: {
+  a: { apiVersion: "v1" }
+  b: { kind: "ConfigMap" }
+  c: { apiVersion: "v1", kind: "ConfigMap" }
+}`,
+			mapper: newFakeRESTMapper([3]string{"", "ConfigMap", "v1"}),
+		},
+		"duplicateResourceTypes": {
+			cue: `
+outputs: {
+  one: { apiVersion: "v1", kind: "ConfigMap" }
+  two: { apiVersion: "v1", kind: "ConfigMap" }
+}`,
+			mapper: newFakeRESTMapper([3]string{"", "ConfigMap", "v1"}),
+		},
+		"malformedApiVersionTrailingSlash": {
+			cue: `
+outputs: {
+  bad: { apiVersion: "apps/", kind: "Deployment" }
+}`,
+			mapper:  newFakeRESTMapper([3]string{"apps", "Deployment", "v1"}),
+			wantErr: "resource type not found on cluster: apps//Deployment",
+		},
+		"deepMetadataNameDoesNotAffectValidation": {
+			cue: `
+outputs: {
+  deep: { apiVersion: "v1", kind: "ConfigMap", metadata: { other: { name: "x" } } }
+}`,
+			mapper: newFakeRESTMapper([3]string{"", "ConfigMap", "v1"}),
+		},
+	}
+
+	for name, tc := range tests {
+		tc := tc
+		t.Run(name, func(t *testing.T) {
+			// Pass nil object for tests - feature gate is disabled by default
+			err := ValidateOutputResourcesExist(tc.cue, tc.mapper, nil)
+			if tc.wantErr != "" {
+				assert.Error(t, err)
+				assert.Contains(t, err.Error(), tc.wantErr)
+			} else {
+				assert.NoError(t, err)
+			}
+		})
+	}
+}
diff --git a/pkg/workflow/providers/legacy/query/handler_test.go b/pkg/workflow/providers/legacy/query/handler_test.go
index 94ffbc7a..35cc52b6 100644
--- a/pkg/workflow/providers/legacy/query/handler_test.go
+++ b/pkg/workflow/providers/legacy/query/handler_test.go
@@ -788,7 +788,7 @@ var _ = Describe("Test Query Provider", func() {
 			},
 			&networkv1.Ingress{
 				TypeMeta: metav1.TypeMeta{
-					APIVersion: "networking.k8s.io/v1beta1",
+					APIVersion: "networking.k8s.io/v1",
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "ingress-helm",
diff --git a/pkg/workflow/providers/multicluster/multicluster_test.go b/pkg/workflow/providers/multicluster/multicluster_test.go
index 47b622f9..bc1ab059 100644
--- a/pkg/workflow/providers/multicluster/multicluster_test.go
+++ b/pkg/workflow/providers/multicluster/multicluster_test.go
@@ -20,30 +20,63 @@ import (
 	"context"
 	"testing"
 
+	"cuelang.org/go/cue"
 	"github.com/stretchr/testify/require"
 	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	clusterv1alpha1 "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
 	clustercommon "github.com/oam-dev/cluster-gateway/pkg/common"
 
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
 	"github.com/oam-dev/kubevela/apis/types"
+	"github.com/oam-dev/kubevela/pkg/appfile"
 	"github.com/oam-dev/kubevela/pkg/multicluster"
-	"github.com/oam-dev/kubevela/pkg/utils/common"
+	commontypes "github.com/oam-dev/kubevela/pkg/utils/common"
 	oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
+
+	wfmock "github.com/kubevela/workflow/pkg/mock"
 )
 
+// mockAction is a mock implementation of types.Action for testing.
+type mockAction struct {
+	wfmock.Action
+	WaitCalled bool
+	WaitReason string
+}
+
+// Wait records that the wait action was called.
+func (a *mockAction) Wait(reason string) {
+	a.WaitCalled = true
+	a.WaitReason = reason
+}
+
 func TestListClusters(t *testing.T) {
-	multicluster.ClusterGatewaySecretNamespace = types.DefaultKubeVelaNS
 	r := require.New(t)
+	originalNS := multicluster.ClusterGatewaySecretNamespace
+	multicluster.ClusterGatewaySecretNamespace = types.DefaultKubeVelaNS
+	t.Cleanup(func() {
+		multicluster.ClusterGatewaySecretNamespace = originalNS
+	})
 	ctx := context.Background()
-	cli := fake.NewClientBuilder().WithScheme(common.Scheme).Build()
+	cli := fake.NewClientBuilder().WithScheme(commontypes.Scheme).Build()
 	clusterNames := []string{"cluster-a", "cluster-b"}
 	for _, secretName := range clusterNames {
-		secret := &corev1.Secret{}
-		secret.Name = secretName
-		secret.Namespace = multicluster.ClusterGatewaySecretNamespace
-		secret.Labels = map[string]string{clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeX509Certificate)}
+		secret := &corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      secretName,
+				Namespace: multicluster.ClusterGatewaySecretNamespace,
+				Labels: map[string]string{
+					clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeX509Certificate),
+				},
+			},
+		}
 		r.NoError(cli.Create(context.Background(), secret))
 	}
 	res, err := ListClusters(ctx, &oamprovidertypes.Params[any]{
@@ -54,3 +87,195 @@ func TestListClusters(t *testing.T) {
 	r.NoError(err)
 	r.Equal(clusterNames, res.Returns.Outputs.Clusters)
 }
+
+func TestDeploy(t *testing.T) {
+	r := require.New(t)
+	ctx := context.Background()
+	cli := fake.NewClientBuilder().WithScheme(commontypes.Scheme).Build()
+
+	// Mock component functions
+	componentApply := func(ctx context.Context, comp common.ApplicationComponent, patcher *cue.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, bool, error) {
+		return nil, nil, true, nil
+	}
+	componentHealthCheck := func(ctx context.Context, comp common.ApplicationComponent, patcher *cue.Value, clusterName string, overrideNamespace string) (bool, *common.ApplicationComponentStatus, *unstructured.Unstructured, []*unstructured.Unstructured, error) {
+		return true, nil, nil, nil, nil
+	}
+	workloadRender := func(ctx context.Context, comp common.ApplicationComponent) (*appfile.Component, error) {
+		return &appfile.Component{}, nil
+	}
+
+	createMockParams := func(parallelism int64) *DeployParams {
+		action := &mockAction{}
+		return &DeployParams{
+			Params: DeployParameter{
+				Parallelism:              parallelism,
+				IgnoreTerraformComponent: true,
+				Policies:                 []string{},
+			},
+			RuntimeParams: oamprovidertypes.RuntimeParams{
+				Action:               action,
+				KubeClient:           cli,
+				ComponentApply:       componentApply,
+				ComponentHealthCheck: componentHealthCheck,
+				WorkloadRender:       workloadRender,
+				Appfile: &appfile.Appfile{
+					Name:      "test-app",
+					Namespace: "default",
+					Policies:  []v1beta1.AppPolicy{},
+				},
+			},
+		}
+	}
+
+	cases := map[string]struct {
+		reason        string
+		params        *DeployParams
+		expectError   bool
+		errorContains string
+		expectPanic   bool
+	}{
+		"parallelism zero validation error": {
+			reason:        "Should return a validation error for zero parallelism",
+			params:        createMockParams(0),
+			expectError:   true,
+			errorContains: "parallelism cannot be smaller than 1",
+		},
+		"parallelism negative validation error": {
+			reason:        "Should return a validation error for negative parallelism",
+			params:        createMockParams(-1),
+			expectError:   true,
+			errorContains: "parallelism cannot be smaller than 1",
+		},
+		"parameters nil pointer handling": {
+			reason:      "Should panic when params are nil",
+			params:      nil,
+			expectPanic: true,
+		},
+		"successful deployment healthy": {
+			reason: "Should execute successfully with valid parameters",
+			params: createMockParams(1),
+		},
+		"successful deployment unhealthy wait": {
+			reason: "Should execute successfully even with higher parallelism",
+			params: createMockParams(2),
+		},
+		"executor deploy error propagation": {
+			reason: "Should pass validation and any errors should be from the executor",
+			params: createMockParams(1),
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			if tc.expectPanic {
+				r.Panics(func() {
+					_, _ = Deploy(ctx, tc.params)
+				})
+				return
+			}
+
+			result, err := Deploy(ctx, tc.params)
+
+			if tc.expectError {
+				r.Error(err)
+				if tc.errorContains != "" {
+					r.Contains(err.Error(), tc.errorContains)
+				}
+			} else {
+				r.NoError(err)
+				r.Nil(result)
+			}
+		})
+	}
+}
+
+func TestGetPlacementsFromTopologyPolicies(t *testing.T) {
+	r := require.New(t)
+	ctx := context.Background()
+	scheme := commontypes.Scheme
+	r.NoError(v1alpha1.AddToScheme(scheme))
+
+	topologyPolicy := &v1alpha1.Policy{
+		ObjectMeta: metav1.ObjectMeta{Name: "my-topology", Namespace: "default"},
+		Type:       v1alpha1.TopologyPolicyType,
+		Properties: &runtime.RawExtension{
+			Raw: []byte(`{"clusters":["local"],"namespace":"topo-ns"}`),
+		},
+	}
+
+	appFileTopologyPolicy := v1beta1.AppPolicy{
+		Name: "my-topology",
+		Type: v1alpha1.TopologyPolicyType,
+		Properties: &runtime.RawExtension{
+			Raw: []byte(`{"clusters":["local"],"namespace":"topo-ns"}`),
+		},
+	}
+
+	cases := map[string]struct {
+		reason             string
+		policiesInAppfile  []v1beta1.AppPolicy
+		policiesToGet      []string
+		objectsToCreate    []client.Object
+		expectedPlacements []v1alpha1.PlacementDecision
+		expectError        bool
+		errorContains      string
+	}{
+		"Successful placement resolution with single policy": {
+			reason:             "Should resolve placement from a single topology policy",
+			objectsToCreate:    []client.Object{topologyPolicy},
+			policiesInAppfile:  []v1beta1.AppPolicy{appFileTopologyPolicy},
+			policiesToGet:      []string{"my-topology"},
+			expectedPlacements: []v1alpha1.PlacementDecision{{Cluster: "local", Namespace: "topo-ns"}},
+		},
+		"Policy not found in appfile": {
+			reason:        "Should return an error if the policy is not found in the appfile",
+			policiesToGet: []string{"non-existent-policy"},
+			expectError:   true,
+			errorContains: "policy non-existent-policy not found",
+		},
+		"Empty policy list returns default local placement": {
+			reason:             "Should return default local placement when no policies are specified",
+			policiesToGet:      []string{},
+			expectedPlacements: []v1alpha1.PlacementDecision{{Cluster: "local"}},
+		},
+		"Nil policy names list returns default local placement": {
+			reason:             "Should return default local placement when the policy list is nil",
+			policiesToGet:      nil,
+			expectedPlacements: []v1alpha1.PlacementDecision{{Cluster: "local"}},
+		},
+		"Empty appfile policies list with a policy name": {
+			reason:        "Should return an error if appfile has no policies but a policy is requested",
+			policiesToGet: []string{"some-policy"},
+			expectError:   true,
+			errorContains: "policy some-policy not found",
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tc.objectsToCreate...).Build()
+			af := &appfile.Appfile{
+				Name:      "test-app",
+				Namespace: "default",
+				Policies:  tc.policiesInAppfile,
+			}
+			params := &PoliciesParams{
+				Params:        PoliciesVars{Policies: tc.policiesToGet},
+				RuntimeParams: oamprovidertypes.RuntimeParams{KubeClient: cli, Appfile: af},
+			}
+
+			result, err := GetPlacementsFromTopologyPolicies(ctx, params)
+
+			if tc.expectError {
+				r.Error(err)
+				if tc.errorContains != "" {
+					r.Contains(err.Error(), tc.errorContains)
+				}
+			} else {
+				r.NoError(err)
+				r.NotNil(result)
+				r.Equal(tc.expectedPlacements, result.Returns.Placements)
+			}
+		})
+	}
+}
diff --git a/pkg/workflow/providers/query/handler_test.go b/pkg/workflow/providers/query/handler_test.go
index ecc43aa0..bdcc05f8 100644
--- a/pkg/workflow/providers/query/handler_test.go
+++ b/pkg/workflow/providers/query/handler_test.go
@@ -788,7 +788,7 @@ var _ = Describe("Test Query Provider", func() {
 			},
 			&networkv1.Ingress{
 				TypeMeta: metav1.TypeMeta{
-					APIVersion: "networking.k8s.io/v1beta1",
+					APIVersion: "networking.k8s.io/v1",
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "ingress-helm",
diff --git a/references/appfile/addon_test.go b/references/appfile/addon_test.go
index 93939c8f..a3790461 100644
--- a/references/appfile/addon_test.go
+++ b/references/appfile/addon_test.go
@@ -55,18 +55,18 @@ var _ = Describe("Test generateSecretFromTerraformOutput", func() {
 	var name = "test-addon-secret"
 	It("namespace doesn't exist", func() {
 		badNamespace := "a-not-existed-namespace"
-		err := generateSecretFromTerraformOutput(k8sClient, nil, name, badNamespace)
+		err := generateSecretFromTerraformOutput(k8sClient, "", name, badNamespace)
 		Expect(err).Should(Equal(fmt.Errorf("namespace %s doesn't exist", badNamespace)))
 	})
 	It("valid output list", func() {
-		outputList := []string{"name=aaa", "age=1"}
-		err := generateSecretFromTerraformOutput(k8sClient, outputList, name, addonNamespace)
+		rawOutput := "name=aaa\nage=1"
+		err := generateSecretFromTerraformOutput(k8sClient, rawOutput, name, addonNamespace)
 		Expect(err).Should(BeNil())
 	})
 
 	It("invalid output list", func() {
-		outputList := []string{"name"}
-		err := generateSecretFromTerraformOutput(k8sClient, outputList, name, addonNamespace)
-		Expect(err).Should(Equal(fmt.Errorf("terraform output isn't in the right format")))
+		rawOutput := "name"
+		err := generateSecretFromTerraformOutput(k8sClient, rawOutput, name, addonNamespace)
+		Expect(err).Should(Equal(fmt.Errorf("terraform output isn't in the right format: %q", rawOutput)))
 	})
 })
diff --git a/references/appfile/api/appfile_test.go b/references/appfile/api/appfile_test.go
index d1e647a7..074b874e 100644
--- a/references/appfile/api/appfile_test.go
+++ b/references/appfile/api/appfile_test.go
@@ -223,7 +223,7 @@ outputs: service: {
 }
 
 outputs: ingress: {
-  apiVersion: "networking.k8s.io/v1beta1"
+  apiVersion: "networking.k8s.io/v1"
   kind: "Ingress"
   spec: {
     rules: [{
@@ -232,9 +232,14 @@ outputs: ingress: {
         paths: [
           for k, v in parameter.http {
             path: k
+            pathType: "Prefix"
             backend: {
-              serviceName: context.name
-              servicePort: v
+              service: {
+                name: context.name
+                port: {
+                  number: v
+                }
+              }
             }
           }
         ]
diff --git a/references/cli/dryrun_test.go b/references/cli/dryrun_test.go
index cb29017e..90fdcbfb 100644
--- a/references/cli/dryrun_test.go
+++ b/references/cli/dryrun_test.go
@@ -285,4 +285,21 @@ var _ = Describe("Testing dry-run", func() {
 		Expect(buff.String()).Should(ContainSubstring(fmt.Sprintf("namespace: %s", appNamespace)))
 	})
 
+	It("Testing dry-run overriding conflicting namespace", func() {
+		// Prepare defs required (reuse existing component/trait definitions if present in cluster)
+		c := common2.Args{}
+		c.SetConfig(cfg)
+		c.SetClient(k8sClient)
+		// Ensure both namespaces exist
+		originalNS := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "original-ns"}}
+		_ = k8sClient.Create(context.Background(), originalNS)
+		forcedNS := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "forced-ns"}}
+		_ = k8sClient.Create(context.Background(), forcedNS)
+
+		opt := DryRunCmdOptions{ApplicationFiles: []string{"test-data/dry-run/testing-dry-run-override.yaml"}, OfflineMode: false}
+		buff, err := DryRunApplication(&opt, c, "forced-ns", "")
+		Expect(err).Should(BeNil())
+		Expect(buff.String()).Should(ContainSubstring("namespace: forced-ns"))
+		Expect(buff.String()).ShouldNot(ContainSubstring("namespace: original-ns"))
+	})
 })
diff --git a/references/cli/top/model/suit_test.go b/references/cli/top/model/suit_test.go
index fcc1cec0..66a7aee5 100644
--- a/references/cli/top/model/suit_test.go
+++ b/references/cli/top/model/suit_test.go
@@ -120,7 +120,7 @@ var _ = BeforeSuite(func() {
 					Kind:       "Ingress",
 					Namespace:  "default",
 					Name:       "ingress-http",
-					APIVersion: "networking.k8s.io/v1beta1",
+					APIVersion: "networking.k8s.io/v1",
 				},
 			},
 			{
diff --git a/references/cli/velaql_test.go b/references/cli/velaql_test.go
index 7eb1c46c..f605813f 100644
--- a/references/cli/velaql_test.go
+++ b/references/cli/velaql_test.go
@@ -117,7 +117,7 @@ var _ = Describe("Test velaQL", func() {
 						Kind:       "Ingress",
 						Namespace:  "default",
 						Name:       "ingress-http",
-						APIVersion: "networking.k8s.io/v1beta1",
+						APIVersion: "networking.k8s.io/v1",
 					},
 				},
 				{
@@ -386,7 +386,7 @@ var _ = Describe("Test velaQL", func() {
 			},
 			&networkv1.Ingress{
 				TypeMeta: metav1.TypeMeta{
-					APIVersion: "networking.k8s.io/v1beta1",
+					APIVersion: "networking.k8s.io/v1",
 				},
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "ingress-helm",
diff --git a/references/common/application_test.go b/references/common/application_test.go
index e8da691d..86c88a3f 100644
--- a/references/common/application_test.go
+++ b/references/common/application_test.go
@@ -16,22 +16,111 @@ limitations under the License.
 package common
 
 import (
+	"bytes"
 	"context"
+	"os"
 	"testing"
 
 	terraformapi "github.com/oam-dev/terraform-controller/api/v1beta2"
 	"github.com/stretchr/testify/assert"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
-	"sigs.k8s.io/controller-runtime/pkg/client"
+	sigs "sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
 	"github.com/oam-dev/kubevela/apis/types"
+	"github.com/oam-dev/kubevela/references/appfile/api"
+
+	utilcommon "github.com/oam-dev/kubevela/pkg/utils/common"
+	querytypes "github.com/oam-dev/kubevela/pkg/utils/types"
+	cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
 )
 
+func TestExportFromAppFile(t *testing.T) {
+	s := runtime.NewScheme()
+	assert.NoError(t, v1beta1.AddToScheme(s))
+	clt := fake.NewClientBuilder().WithScheme(s).Build()
+	var out bytes.Buffer
+	ioStream := cmdutil.IOStreams{In: os.Stdin, Out: &out, ErrOut: &out}
+
+	o := &AppfileOptions{
+		Kubecli:   clt,
+		IO:        ioStream,
+		Namespace: "default",
+	}
+
+	appFile := &api.AppFile{
+		Name: "test-app-export-from",
+	}
+
+	c := utilcommon.Args{}
+	c.SetClient(clt)
+
+	result, data, err := o.ExportFromAppFile(appFile, "default", true, c)
+	assert.NoError(t, err)
+	assert.NotNil(t, result)
+	assert.NotNil(t, data)
+	assert.Contains(t, string(data), "name: test-app-export-from")
+	assert.Equal(t, "test-app-export-from", result.application.Name)
+}
+
+func TestApplyApp(t *testing.T) {
+	s := runtime.NewScheme()
+	v1beta1.AddToScheme(s)
+
+	t.Run("create app if not exist", func(t *testing.T) {
+		cltCreate := fake.NewClientBuilder().WithScheme(s).Build()
+		var outCreate bytes.Buffer
+		ioStreamCreate := cmdutil.IOStreams{In: os.Stdin, Out: &outCreate, ErrOut: &outCreate}
+		oCreate := &AppfileOptions{
+			Kubecli:   cltCreate,
+			IO:        ioStreamCreate,
+			Namespace: "default",
+		}
+		appCreate := &v1beta1.Application{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "test-app-apply",
+				Namespace: "default",
+			},
+		}
+		err := oCreate.ApplyApp(appCreate, nil)
+		assert.NoError(t, err)
+		assert.Contains(t, outCreate.String(), "App has not been deployed, creating a new deployment...")
+		assert.Contains(t, outCreate.String(), "vela port-forward test-app-apply")
+	})
+
+	t.Run("update app if exists", func(t *testing.T) {
+		existingApp := &v1beta1.Application{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "test-app-apply",
+				Namespace: "default",
+			},
+		}
+		cltUpdate := fake.NewClientBuilder().WithScheme(s).WithObjects(existingApp).Build()
+		var outUpdate bytes.Buffer
+		ioStreamUpdate := cmdutil.IOStreams{In: os.Stdin, Out: &outUpdate, ErrOut: &outUpdate}
+		oUpdate := &AppfileOptions{
+			Kubecli:   cltUpdate,
+			IO:        ioStreamUpdate,
+			Namespace: "default",
+		}
+		appUpdate := &v1beta1.Application{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "test-app-apply",
+				Namespace: "default",
+			},
+		}
+		err := oUpdate.ApplyApp(appUpdate, nil)
+		assert.NoError(t, err)
+		assert.Contains(t, outUpdate.String(), "App exists, updating existing deployment...")
+		assert.Contains(t, outUpdate.String(), "vela port-forward test-app-apply")
+	})
+}
+
 func TestPrepareToForceDeleteTerraformComponents(t *testing.T) {
 	ctx := context.Background()
 	s := runtime.NewScheme()
@@ -95,7 +184,7 @@ func TestPrepareToForceDeleteTerraformComponents(t *testing.T) {
 
 	k8sClient5 := fake.NewClientBuilder().WithScheme(s).WithObjects(app2, def2, conf2).Build()
 	type args struct {
-		k8sClient client.Client
+		k8sClient sigs.Client
 		namespace string
 		name      string
 	}
@@ -176,5 +265,115 @@ func TestPrepareToForceDeleteTerraformComponents(t *testing.T) {
 			}
 		})
 	}
+}
+
+func TestIsAppfile(t *testing.T) {
+	testCases := []struct {
+		name     string
+		data     []byte
+		expected bool
+	}{
+		{
+			name:     "valid appfile json",
+			data:     []byte(`{"name": "test"}`),
+			expected: true,
+		},
+		{
+			name:     "invalid json",
+			data:     []byte(`{"name": "test"`),
+			expected: false,
+		},
+		{
+			name: "application yaml",
+			data: []byte(`
+apiVersion: core.oam.dev/v1beta1
+kind: Application
+metadata:
+  name: test-app
+`),
+			expected: false,
+		},
+		{
+			name: "appfile yaml",
+			data: []byte(`
+name: test-app
+`),
+			expected: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			result := IsAppfile(tc.data)
+			assert.Equal(t, tc.expected, result)
+		})
+	}
+}
+
+func TestInfo(t *testing.T) {
+	app := &v1beta1.Application{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-app",
+			Namespace: "test-ns",
+		},
+	}
+	info := Info(app)
+	assert.Contains(t, info, "vela port-forward test-app -n test-ns")
+	assert.Contains(t, info, "vela exec test-app -n test-ns")
+	assert.Contains(t, info, "vela logs test-app -n test-ns")
+	assert.Contains(t, info, "vela status test-app -n test-ns")
+	assert.Contains(t, info, "vela status test-app -n test-ns --endpoint")
+}
 
+func TestSonLeafResource(t *testing.T) {
+	node := &querytypes.ResourceTreeNode{
+		LeafNodes: []*querytypes.ResourceTreeNode{
+			{
+				Object: &unstructured.Unstructured{
+					Object: map[string]interface{}{
+						"kind":       "Deployment",
+						"apiVersion": "apps/v1",
+					},
+				},
+				Kind:       "Deployment",
+				APIVersion: "apps/v1",
+			},
+		},
+	}
+	resources := sonLeafResource(node, "Deployment", "apps/v1")
+	assert.Equal(t, 1, len(resources))
+	assert.Equal(t, "Deployment", resources[0].GetKind())
+}
+
+func TestLoadAppFile(t *testing.T) {
+	content := "name: test-app"
+	tmpFile, err := os.CreateTemp("", "appfile-*.yaml")
+	assert.NoError(t, err)
+	defer os.Remove(tmpFile.Name())
+
+	_, err = tmpFile.WriteString(content)
+	assert.NoError(t, err)
+	assert.NoError(t, tmpFile.Close())
+
+	appFile, err := LoadAppFile(tmpFile.Name())
+	assert.NoError(t, err)
+	assert.NotNil(t, appFile)
+	assert.Equal(t, "test-app", appFile.Name)
+}
+
+func TestApplyApplication(t *testing.T) {
+	s := runtime.NewScheme()
+	v1beta1.AddToScheme(s)
+	clt := fake.NewClientBuilder().WithScheme(s).Build()
+	app := v1beta1.Application{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-app",
+			Namespace: "default",
+		},
+	}
+	var out bytes.Buffer
+	ioStream := cmdutil.IOStreams{In: os.Stdin, Out: &out, ErrOut: &out}
+	err := ApplyApplication(app, ioStream, clt)
+	assert.NoError(t, err)
+	assert.Contains(t, out.String(), "Applying an application in vela K8s object format...")
 }
diff --git a/references/common/metrics_test.go b/references/common/metrics_test.go
index e751aa90..5ab7044c 100644
--- a/references/common/metrics_test.go
+++ b/references/common/metrics_test.go
@@ -18,39 +18,50 @@ package common
 
 import (
 	"testing"
-	"time"
-
-	"k8s.io/utils/ptr"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/envtest"
-
-	"github.com/oam-dev/kubevela/pkg/utils/common"
 
 	"github.com/stretchr/testify/assert"
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/metrics/pkg/apis/metrics/v1beta1"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
+	appv1beta1 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
 )
 
 func TestToPercentageStr(t *testing.T) {
-	var v1, v2 int64
-	v1, v2 = 10, 100
-	assert.Equal(t, ToPercentageStr(v1, v2), "10%")
-	v1, v2 = 10, 0
-	assert.Equal(t, ToPercentageStr(v1, v2), "N/A")
+	t.Run("valid percentage", func(t *testing.T) {
+		var v1, v2 int64 = 10, 100
+		assert.Equal(t, "10%", ToPercentageStr(v1, v2))
+	})
+	t.Run("division by zero", func(t *testing.T) {
+		var v1, v2 int64 = 10, 0
+		assert.Equal(t, "N/A", ToPercentageStr(v1, v2))
+	})
 }
 
-func TestGetPodResourceSpecAndUsage(t *testing.T) {
-	testEnv := &envtest.Environment{
-		ControlPlaneStartTimeout: time.Minute * 3,
-		ControlPlaneStopTimeout:  time.Minute,
-		UseExistingCluster:       ptr.To(false),
-	}
-	cfg, err := testEnv.Start()
-	assert.NoError(t, err)
+func TestToPercentage(t *testing.T) {
+	t.Run("valid percentage", func(t *testing.T) {
+		var v1, v2 int64 = 10, 100
+		assert.Equal(t, 10, ToPercentage(v1, v2))
+	})
+	t.Run("division by zero", func(t *testing.T) {
+		var v1, v2 int64 = 10, 0
+		assert.Equal(t, 0, ToPercentage(v1, v2))
+	})
+	t.Run("floor", func(t *testing.T) {
+		var v1, v2 int64 = 1, 3
+		assert.Equal(t, 33, ToPercentage(v1, v2))
+	})
+}
 
-	k8sClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
-	assert.NoError(t, err)
+func TestGetPodResourceSpecAndUsage(t *testing.T) {
+	s := runtime.NewScheme()
+	v1.AddToScheme(s)
+	k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
 
 	quantityLimitsCPU, _ := resource.ParseQuantity("10m")
 	quantityLimitsMemory, _ := resource.ParseQuantity("10Mi")
@@ -83,10 +94,157 @@ func TestGetPodResourceSpecAndUsage(t *testing.T) {
 	}
 
 	spec, usage := GetPodResourceSpecAndUsage(k8sClient, pod, podMetric)
-	assert.Equal(t, usage.CPU, int64(8))
-	assert.Equal(t, usage.Mem, int64(20971520))
-	assert.Equal(t, spec.Lcpu, int64(10))
-	assert.Equal(t, spec.Lmem, int64(10485760))
-	assert.Equal(t, spec.Rcpu, int64(100))
-	assert.Equal(t, spec.Rmem, int64(52428800))
+	assert.Equal(t, int64(8), usage.CPU)
+	assert.Equal(t, int64(20971520), usage.Mem)
+	assert.Equal(t, int64(10), spec.Lcpu)
+	assert.Equal(t, int64(10485760), spec.Lmem)
+	assert.Equal(t, int64(100), spec.Rcpu)
+	assert.Equal(t, int64(52428800), spec.Rmem)
+}
+
+func TestGetPodStorage(t *testing.T) {
+	s := runtime.NewScheme()
+	v1.AddToScheme(s)
+
+	pvc := &v1.PersistentVolumeClaim{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-pvc",
+			Namespace: "default",
+		},
+	}
+	k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(pvc).Build()
+
+	podWithPVC := &v1.Pod{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "default",
+		},
+		Spec: v1.PodSpec{
+			Volumes: []v1.Volume{
+				{
+					Name: "test-storage",
+					VolumeSource: v1.VolumeSource{
+						PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
+							ClaimName: "test-pvc",
+						},
+					},
+				},
+			},
+		},
+	}
+
+	podWithoutPVC := &v1.Pod{
+		Spec: v1.PodSpec{
+			Volumes: []v1.Volume{
+				{
+					Name: "test-emptydir",
+					VolumeSource: v1.VolumeSource{
+						EmptyDir: &v1.EmptyDirVolumeSource{},
+					},
+				},
+			},
+		},
+	}
+
+	podWithNonExistentPVC := &v1.Pod{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "default",
+		},
+		Spec: v1.PodSpec{
+			Volumes: []v1.Volume{
+				{
+					Name: "test-storage",
+					VolumeSource: v1.VolumeSource{
+						PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
+							ClaimName: "non-existent-pvc",
+						},
+					},
+				},
+			},
+		},
+	}
+
+	t.Run("pod with pvc", func(t *testing.T) {
+		storages := GetPodStorage(k8sClient, podWithPVC)
+		assert.Equal(t, 1, len(storages))
+		assert.Equal(t, "test-pvc", storages[0].Name)
+	})
+
+	t.Run("pod without pvc", func(t *testing.T) {
+		storages := GetPodStorage(k8sClient, podWithoutPVC)
+		assert.Equal(t, 0, len(storages))
+	})
+
+	t.Run("pod with non-existent pvc", func(t *testing.T) {
+		storages := GetPodStorage(k8sClient, podWithNonExistentPVC)
+		assert.Equal(t, 0, len(storages))
+	})
+}
+
+func TestGetPodOfManagedResource(t *testing.T) {
+	s := runtime.NewScheme()
+	v1.AddToScheme(s)
+	appv1beta1.AddToScheme(s)
+
+	app := &appv1beta1.Application{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-app",
+			Namespace: "default",
+		},
+	}
+
+	pod := &v1.Pod{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Pod",
+			APIVersion: "v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-pod",
+			Namespace: "default",
+		},
+	}
+	podUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(pod)
+	assert.NoError(t, err)
+
+	rt := &appv1beta1.ResourceTracker{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-app-v1", // A tracker name
+			Namespace: "default",
+			Labels: map[string]string{
+				"app.oam.dev/name":      "test-app",
+				"app.oam.dev/namespace": "default",
+			},
+		},
+		Spec: appv1beta1.ResourceTrackerSpec{
+			Type: appv1beta1.ResourceTrackerTypeRoot,
+			ManagedResources: []appv1beta1.ManagedResource{
+				{
+					ClusterObjectReference: common.ClusterObjectReference{
+						ObjectReference: v1.ObjectReference{
+							Kind:       "Pod",
+							APIVersion: "v1",
+							Name:       "test-pod",
+							Namespace:  "default",
+						},
+					},
+					OAMObjectReference: common.OAMObjectReference{
+						Component: "test-component",
+					},
+				},
+			},
+		},
+	}
+
+	k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(app, rt, &unstructured.Unstructured{Object: podUnstructured}).Build()
+
+	t.Run("get existing pod", func(t *testing.T) {
+		pods := GetPodOfManagedResource(k8sClient, app, "test-component")
+		assert.Equal(t, 2, len(pods))
+		assert.Equal(t, "test-pod", pods[0].Name)
+		assert.Equal(t, "test-pod", pods[1].Name)
+	})
+
+	t.Run("get pod for non-existent component", func(t *testing.T) {
+		pods := GetPodOfManagedResource(k8sClient, app, "non-existent-component")
+		assert.Equal(t, 0, len(pods))
+	})
 }
diff --git a/references/common/registry_test.go b/references/common/registry_test.go
index 44c6fe79..d484ad08 100644
--- a/references/common/registry_test.go
+++ b/references/common/registry_test.go
@@ -17,16 +17,178 @@ limitations under the License.
 package common
 
 import (
+	"bytes"
+	"context"
 	"encoding/json"
+	"os"
 	"reflect"
 	"testing"
 
+	"github.com/pkg/errors"
 	"github.com/stretchr/testify/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+	"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
 
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
 	"github.com/oam-dev/kubevela/apis/types"
+	cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
 )
 
+func TestInstallComponentDefinition(t *testing.T) {
+	s := runtime.NewScheme()
+	assert.NoError(t, v1beta1.AddToScheme(s))
+
+	validComponentData := []byte(`
+apiVersion: core.oam.dev/v1beta1
+kind: ComponentDefinition
+metadata:
+  name: test-component
+spec:
+  workload:
+    definition:
+      apiVersion: apps/v1
+      kind: Deployment
+`)
+
+	existingComponent := &v1beta1.ComponentDefinition{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-component",
+			Namespace: types.DefaultKubeVelaNS,
+		},
+	}
+
+	t.Run("success", func(t *testing.T) {
+		k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
+		var out bytes.Buffer
+		ioStreams := cmdutil.IOStreams{In: os.Stdin, Out: &out, ErrOut: &out}
+
+		err := InstallComponentDefinition(k8sClient, validComponentData, ioStreams)
+		assert.NoError(t, err)
+		assert.Contains(t, out.String(), "Installing component: test-component")
+
+		var cd v1beta1.ComponentDefinition
+		err = k8sClient.Get(context.Background(), client.ObjectKey{Name: "test-component", Namespace: types.DefaultKubeVelaNS}, &cd)
+		assert.NoError(t, err)
+		assert.Equal(t, "test-component", cd.Name)
+	})
+
+	t.Run("already exists", func(t *testing.T) {
+		k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(existingComponent).Build()
+		var out bytes.Buffer
+		ioStreams := cmdutil.IOStreams{In: os.Stdin, Out: &out, ErrOut: &out}
+
+		err := InstallComponentDefinition(k8sClient, validComponentData, ioStreams)
+		assert.NoError(t, err)
+	})
+
+	t.Run("client error", func(t *testing.T) {
+		k8sClient := fake.NewClientBuilder().WithScheme(s).WithInterceptorFuncs(interceptor.Funcs{
+			Create: func(ctx context.Context, cl client.WithWatch, obj client.Object, opts ...client.CreateOption) error {
+				return errors.New("client create error")
+			},
+		}).Build()
+		var out bytes.Buffer
+		ioStreams := cmdutil.IOStreams{In: os.Stdin, Out: &out, ErrOut: &out}
+
+		err := InstallComponentDefinition(k8sClient, validComponentData, ioStreams)
+		assert.Error(t, err)
+		assert.EqualError(t, err, "client create error")
+	})
+
+	t.Run("invalid data", func(t *testing.T) {
+		k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
+		var out bytes.Buffer
+		ioStreams := cmdutil.IOStreams{In: os.Stdin, Out: &out, ErrOut: &out}
+		invalidData := []byte(`{"invalid": "yaml"`)
+
+		err := InstallComponentDefinition(k8sClient, invalidData, ioStreams)
+		assert.Error(t, err)
+	})
+
+	t.Run("nil data", func(t *testing.T) {
+		k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
+		var out bytes.Buffer
+		ioStreams := cmdutil.IOStreams{In: os.Stdin, Out: &out, ErrOut: &out}
+
+		err := InstallComponentDefinition(k8sClient, nil, ioStreams)
+		assert.Error(t, err)
+		assert.Equal(t, "componentData is nil", err.Error())
+	})
+}
+
+func TestInstallTraitDefinition(t *testing.T) {
+	s := runtime.NewScheme()
+	v1beta1.AddToScheme(s)
+
+	validTraitData := []byte(`
+apiVersion: core.oam.dev/v1beta1
+kind: TraitDefinition
+metadata:
+  name: test-trait
+spec:
+  appliesToWorkloads:
+    - deployments.apps
+`)
+
+	existingTrait := &v1beta1.TraitDefinition{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-trait",
+			Namespace: types.DefaultKubeVelaNS,
+		},
+	}
+
+	t.Run("success", func(t *testing.T) {
+		k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
+		var out bytes.Buffer
+		ioStreams := cmdutil.IOStreams{In: os.Stdin, Out: &out, ErrOut: &out}
+
+		err := InstallTraitDefinition(k8sClient, validTraitData, ioStreams)
+		assert.NoError(t, err)
+		assert.Contains(t, out.String(), "Installing trait test-trait")
+
+		var td v1beta1.TraitDefinition
+		err = k8sClient.Get(context.Background(), client.ObjectKey{Name: "test-trait", Namespace: types.DefaultKubeVelaNS}, &td)
+		assert.NoError(t, err)
+		assert.Equal(t, "test-trait", td.Name)
+	})
+
+	t.Run("already exists", func(t *testing.T) {
+		k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(existingTrait).Build()
+		var out bytes.Buffer
+		ioStreams := cmdutil.IOStreams{In: os.Stdin, Out: &out, ErrOut: &out}
+
+		err := InstallTraitDefinition(k8sClient, validTraitData, ioStreams)
+		assert.NoError(t, err)
+	})
+
+	t.Run("client error", func(t *testing.T) {
+		k8sClient := fake.NewClientBuilder().WithScheme(s).WithInterceptorFuncs(interceptor.Funcs{
+			Create: func(ctx context.Context, cl client.WithWatch, obj client.Object, opts ...client.CreateOption) error {
+				return errors.New("client create error")
+			},
+		}).Build()
+		var out bytes.Buffer
+		ioStreams := cmdutil.IOStreams{In: os.Stdin, Out: &out, ErrOut: &out}
+
+		err := InstallTraitDefinition(k8sClient, validTraitData, ioStreams)
+		assert.Error(t, err)
+		assert.Equal(t, "client create error", err.Error())
+	})
+
+	t.Run("invalid data", func(t *testing.T) {
+		k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
+		var out bytes.Buffer
+		ioStreams := cmdutil.IOStreams{In: os.Stdin, Out: &out, ErrOut: &out}
+		invalidData := []byte(`{"invalid": "yaml"`)
+
+		err := InstallTraitDefinition(k8sClient, invalidData, ioStreams)
+		assert.Error(t, err)
+	})
+}
+
 func TestAddSourceIntoDefinition(t *testing.T) {
 	caseJson := []byte(`{"template":""}`)
 	wantJson := []byte(`{"source":{"repoName":"foo"},"template":""}`)
diff --git a/references/common/trait_test.go b/references/common/trait_test.go
new file mode 100644
index 00000000..20879c10
--- /dev/null
+++ b/references/common/trait_test.go
@@ -0,0 +1,90 @@
+/*
+Copyright 2021 The KubeVela Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package common
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
+	"github.com/oam-dev/kubevela/pkg/oam"
+	"github.com/oam-dev/kubevela/pkg/utils/common"
+)
+
+func TestListRawWorkloadDefinitions(t *testing.T) {
+	s := runtime.NewScheme()
+	v1beta1.AddToScheme(s)
+
+	sysWorkload := v1beta1.WorkloadDefinition{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "sys-worker",
+			Namespace: oam.SystemDefinitionNamespace,
+		},
+	}
+	userWorkload := v1beta1.WorkloadDefinition{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "user-worker",
+			Namespace: "my-ns",
+		},
+	}
+
+	t.Run("list from both user and system namespaces", func(t *testing.T) {
+		k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(&sysWorkload, &userWorkload).Build()
+		c := common.Args{}
+		c.SetClient(k8sClient)
+
+		defs, err := ListRawWorkloadDefinitions("my-ns", c)
+		assert.NoError(t, err)
+		assert.Equal(t, 2, len(defs))
+	})
+
+	t.Run("list from only system namespace", func(t *testing.T) {
+		k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(&sysWorkload).Build()
+		c := common.Args{}
+		c.SetClient(k8sClient)
+
+		defs, err := ListRawWorkloadDefinitions("my-ns", c)
+		assert.NoError(t, err)
+		assert.Equal(t, 1, len(defs))
+		assert.Equal(t, "sys-worker", defs[0].Name)
+	})
+
+	t.Run("list from only user namespace", func(t *testing.T) {
+		k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(&userWorkload).Build()
+		c := common.Args{}
+		c.SetClient(k8sClient)
+
+		defs, err := ListRawWorkloadDefinitions("my-ns", c)
+		assert.NoError(t, err)
+		assert.Equal(t, 1, len(defs))
+		assert.Equal(t, "user-worker", defs[0].Name)
+	})
+
+	t.Run("no definitions found", func(t *testing.T) {
+		k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
+		c := common.Args{}
+		c.SetClient(k8sClient)
+
+		defs, err := ListRawWorkloadDefinitions("my-ns", c)
+		assert.NoError(t, err)
+		assert.Equal(t, 0, len(defs))
+	})
+}
diff --git a/references/common/workload_test.go b/references/common/workload_test.go
new file mode 100644
index 00000000..4549d95a
--- /dev/null
+++ b/references/common/workload_test.go
@@ -0,0 +1,106 @@
+/*
+Copyright 2021 The KubeVela Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package common
+
+import (
+	"testing"
+
+	"github.com/spf13/pflag"
+	"github.com/stretchr/testify/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+	corecommon "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
+	"github.com/oam-dev/kubevela/apis/types"
+	"github.com/oam-dev/kubevela/pkg/oam"
+	"github.com/oam-dev/kubevela/pkg/utils/common"
+)
+
+func TestInitApplication(t *testing.T) {
+	c := common.Args{}
+	c.SetClient(fake.NewClientBuilder().Build())
+
+	t.Run("with appGroup", func(t *testing.T) {
+		app, err := InitApplication("default", c, "my-workload", "my-app")
+		assert.NoError(t, err)
+		assert.NotNil(t, app)
+		assert.Equal(t, "my-app", app.Name)
+	})
+
+	t.Run("without appGroup", func(t *testing.T) {
+		app, err := InitApplication("default", c, "my-workload", "")
+		assert.NoError(t, err)
+		assert.NotNil(t, app)
+		assert.Equal(t, "my-workload", app.Name)
+	})
+}
+
+func TestBaseComplete(t *testing.T) {
+	s := runtime.NewScheme()
+	assert.NoError(t, v1beta1.AddToScheme(s))
+
+	template := &v1beta1.ComponentDefinition{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "worker",
+			Namespace: oam.SystemDefinitionNamespace,
+		},
+		Spec: v1beta1.ComponentDefinitionSpec{
+			Workload: corecommon.WorkloadTypeDescriptor{
+				Type: types.AutoDetectWorkloadDefinition,
+			},
+			Schematic: &corecommon.Schematic{
+				CUE: &corecommon.CUE{
+					Template: `
+parameter: {
+	image: string
+	port: *8080 | int
+}
+`,
+				},
+			},
+		},
+	}
+	k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(template).Build()
+	c := common.Args{}
+	c.SetClient(k8sClient)
+
+	t.Run("success", func(t *testing.T) {
+		flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
+		flagSet.String("image", "my-image", "")
+		flagSet.Int64("port", 80, "")
+
+		app, err := BaseComplete(oam.SystemDefinitionNamespace, c, "my-workload", "my-app", flagSet, "worker")
+		assert.NoError(t, err)
+		assert.NotNil(t, app)
+
+		workload, ok := app.Services["my-workload"]
+		assert.True(t, ok)
+		assert.Equal(t, "my-image", workload["image"])
+		assert.Equal(t, int64(80), workload["port"])
+	})
+
+	t.Run("missing required flag", func(t *testing.T) {
+		flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
+		// not setting the required "image" flag
+
+		_, err := BaseComplete(oam.SystemDefinitionNamespace, c, "my-workload", "my-app", flagSet, "worker")
+		assert.Error(t, err)
+		assert.Contains(t, err.Error(), `required flag(s) "image" not set`)
+	})
+}
diff --git a/test/e2e-addon-test/addon_test.go b/test/e2e-addon-test/addon_test.go
index 6385bff0..fcccfa9c 100644
--- a/test/e2e-addon-test/addon_test.go
+++ b/test/e2e-addon-test/addon_test.go
@@ -21,17 +21,22 @@ import (
 	"errors"
 	"fmt"
 	"math/rand"
+
 	"os/exec"
 	"strconv"
+
 	"time"
 
 	terraformv1beta1 "github.com/oam-dev/terraform-controller/api/v1beta1"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
+	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
+	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
+
 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
 	"github.com/oam-dev/kubevela/pkg/oam/util"
 	"github.com/oam-dev/kubevela/pkg/utils/common"
@@ -132,4 +137,41 @@ var _ = Describe("Addon tests", func() {
 		Expect(err).Should(BeNil())
 		Expect(string(output)).Should(ContainSubstring("enabled successfully"))
 	})
+
+	It("Addon Workflow is successfully enabled and WorkflowRun creates Deployment", func() {
+		By("Install Addon Workflow")
+
+		output, err := exec.Command("bash", "-c", "/tmp/vela addon enable vela-workflow").Output()
+		var ee *exec.ExitError
+		if errors.As(err, &ee) {
+			fmt.Println("exit code error:", string(ee.Stderr))
+		}
+		Expect(err).Should(BeNil())
+		Expect(string(output)).Should(ContainSubstring("enabled successfully"))
+
+		By("Apply a WorkflowRun which creates a Deployment")
+		var wr workflowv1alpha1.WorkflowRun
+		Expect(common.ReadYamlToObject("./testdata/workflow/workflowrun_nginx.yaml", &wr)).Should(BeNil())
+		// set namespace to dynamically created test namespace
+		wr.Namespace = namespaceName
+		Eventually(func() error { return k8sClient.Create(ctx, wr.DeepCopy()) }, 20*time.Second, 500*time.Millisecond).Should(Succeed())
+
+		By("Verify the Deployment is created by WorkflowRun")
+		Eventually(func() error {
+			var deploy appsv1.Deployment
+			return k8sClient.Get(ctx, client.ObjectKey{Name: "apply-nginx-deployment", Namespace: namespaceName}, &deploy)
+		}, 180*time.Second, 2*time.Second).Should(Succeed())
+
+		By("Check the WorkflowRun reaches a succeeded phase")
+		Eventually(func() error {
+			var latest workflowv1alpha1.WorkflowRun
+			if err := k8sClient.Get(ctx, client.ObjectKey{Name: wr.Name, Namespace: namespaceName}, &latest); err != nil {
+				return err
+			}
+			if latest.Status.Phase == workflowv1alpha1.WorkflowStateSucceeded {
+				return nil
+			}
+			return fmt.Errorf("workflowrun not finished, current phase: %s", latest.Status.Phase)
+		}, 300*time.Second, 2*time.Second).Should(Succeed())
+	})
 })
diff --git a/test/e2e-addon-test/suite_test.go b/test/e2e-addon-test/suite_test.go
index c68fcee0..a8a00230 100644
--- a/test/e2e-addon-test/suite_test.go
+++ b/test/e2e-addon-test/suite_test.go
@@ -21,6 +21,7 @@ import (
 	"testing"
 	"time"
 
+	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
 	terraformv1beta1 "github.com/oam-dev/terraform-controller/api/v1beta1"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
@@ -57,6 +58,8 @@ var _ = BeforeSuite(func() {
 	Expect(err).Should(BeNil())
 	err = terraformv1beta1.AddToScheme(scheme)
 	Expect(err).Should(BeNil())
+	err = workflowv1alpha1.AddToScheme(scheme)
+	Expect(err).Should(BeNil())
 	By("Setting up kubernetes client")
 	k8sClient, err = client.New(config.GetConfigOrDie(), client.Options{Scheme: scheme})
 	if err != nil {
diff --git a/test/e2e-addon-test/testdata/app/app_terraform_pod.yaml b/test/e2e-addon-test/testdata/app/app_terraform_pod.yaml
new file mode 100644
index 00000000..94f818ce
--- /dev/null
+++ b/test/e2e-addon-test/testdata/app/app_terraform_pod.yaml
@@ -0,0 +1,12 @@
+apiVersion: core.oam.dev/v1beta1
+kind: Application
+metadata:
+  name: terraform-pod-sample
+spec:
+  components:
+    - name: sample-pod
+      type: terraform-k8s-pod
+      properties:
+        name: tf-sample-pod
+        namespace: ___NAMESPACE___
+        image: nginx:1.25-alpine
diff --git a/test/e2e-addon-test/testdata/definitions/terraform_pod_component.yaml b/test/e2e-addon-test/testdata/definitions/terraform_pod_component.yaml
new file mode 100644
index 00000000..a5c0859f
--- /dev/null
+++ b/test/e2e-addon-test/testdata/definitions/terraform_pod_component.yaml
@@ -0,0 +1,65 @@
+apiVersion: core.oam.dev/v1beta1
+kind: ComponentDefinition
+metadata:
+  name: terraform-k8s-pod
+  namespace: vela-system
+  annotations:
+    definition.oam.dev/description: Terraform configuration to create a Kubernetes Pod via the kubernetes provider
+  labels:
+    type: terraform
+spec:
+  workload:
+    definition:
+      apiVersion: terraform.core.oam.dev/v1beta1
+      kind: Configuration
+  schematic:
+    terraform:
+      configuration: |
+        terraform {
+          required_providers {
+            kubernetes = {
+              source  = "hashicorp/kubernetes"
+              version = "~> 2.27"
+            }
+          }
+        }
+
+        # In-cluster provider auth (terraform-controller should run in cluster and inject)
+        provider "kubernetes" {}
+
+        resource "kubernetes_pod" "example" {
+          metadata {
+            name      = var.name
+            namespace = var.namespace
+            labels = {
+              app = "tf-sample"
+            }
+          }
+          spec {
+            container {
+              image = var.image
+              name  = "nginx"
+              port {
+                container_port = 80
+              }
+            }
+          }
+        }
+
+        output "POD_NAME" {
+          value = kubernetes_pod.example.metadata[0].name
+        }
+
+        variable "name" {
+          type = string
+          description = "Name of the Pod to create"
+        }
+        variable "namespace" {
+          type = string
+          description = "Namespace in which to create the Pod"
+        }
+        variable "image" {
+          type    = string
+          default = "nginx:1.25-alpine"
+          description = "Container image"
+        }
diff --git a/test/e2e-addon-test/testdata/workflow/workflowrun_nginx.yaml b/test/e2e-addon-test/testdata/workflow/workflowrun_nginx.yaml
new file mode 100644
index 00000000..6a1f06d5
--- /dev/null
+++ b/test/e2e-addon-test/testdata/workflow/workflowrun_nginx.yaml
@@ -0,0 +1,13 @@
+apiVersion: core.oam.dev/v1alpha1
+kind: WorkflowRun
+metadata:
+  name: deploy-nginx
+  namespace: default
+spec:
+  workflowSpec:
+    steps:
+      - name: apply-nginx-deployment
+        type: apply-deployment
+        properties:
+          image: nginx:latest
+          replicas: 1 
\ No newline at end of file
diff --git a/test/e2e-test/definition_output_validation_test.go b/test/e2e-test/definition_output_validation_test.go
new file mode 100644
index 00000000..567dbff2
--- /dev/null
+++ b/test/e2e-test/definition_output_validation_test.go
@@ -0,0 +1,1188 @@
+/*
+ Copyright 2021. The KubeVela Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package controllers_test
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
+	"github.com/oam-dev/kubevela/pkg/oam/util"
+)
+
+var _ = Describe("Definition Output Validation E2E tests", func() {
+	ctx := context.Background()
+
+	var namespace string
+	var ns corev1.Namespace
+
+	BeforeEach(func() {
+		namespace = randomNamespaceName("def-output-validation")
+		ns = corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
+
+		Eventually(func() error {
+			return k8sClient.Create(ctx, &ns)
+		}, time.Second*3, time.Microsecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
+	})
+
+	AfterEach(func() {
+		By("Clean up resources after a test")
+		k8sClient.DeleteAllOf(ctx, &v1beta1.ComponentDefinition{}, client.InNamespace(namespace))
+		k8sClient.DeleteAllOf(ctx, &v1beta1.TraitDefinition{}, client.InNamespace(namespace))
+		k8sClient.DeleteAllOf(ctx, &v1beta1.PolicyDefinition{}, client.InNamespace(namespace))
+		k8sClient.DeleteAllOf(ctx, &v1beta1.WorkflowStepDefinition{}, client.InNamespace(namespace))
+
+		By(fmt.Sprintf("Delete the entire namespace %s", ns.Name))
+		Expect(k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))).Should(Succeed())
+	})
+
+	Context("Test validation for outputs with non-existent CRDs", func() {
+
+		It("Should reject ComponentDefinition with non-existent CRD in outputs", func() {
+			componentDef := &v1beta1.ComponentDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "ComponentDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-comp-with-invalid-output",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.ComponentDefinitionSpec{
+					Workload: common.WorkloadTypeDescriptor{
+						Definition: common.WorkloadGVK{
+							APIVersion: "apps/v1",
+							Kind:       "Deployment",
+						},
+					},
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	name: string
+	image: string
+}
+
+output: {
+	apiVersion: "apps/v1"
+	kind: "Deployment"
+	metadata: name: parameter.name
+	spec: {
+		selector: matchLabels: app: parameter.name
+		template: {
+			metadata: labels: app: parameter.name
+			spec: containers: [{
+				name: parameter.name
+				image: parameter.image
+			}]
+		}
+	}
+}
+
+outputs: {
+	nonExistentResource: {
+		apiVersion: "custom.io/v1alpha1"
+		kind: "NonExistentResource"
+		metadata: name: parameter.name + "-custom"
+		spec: {
+			foo: "bar"
+		}
+	}
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, componentDef)
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("resource type not found on cluster"))
+		})
+
+		It("Should accept ComponentDefinition with only valid resources in outputs", func() {
+			componentDef := &v1beta1.ComponentDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "ComponentDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-comp-with-valid-outputs",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.ComponentDefinitionSpec{
+					Workload: common.WorkloadTypeDescriptor{
+						Definition: common.WorkloadGVK{
+							APIVersion: "apps/v1",
+							Kind:       "Deployment",
+						},
+					},
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	name: string
+	image: string
+}
+
+output: {
+	apiVersion: "apps/v1"
+	kind: "Deployment"
+	metadata: name: parameter.name
+	spec: {
+		selector: matchLabels: app: parameter.name
+		template: {
+			metadata: labels: app: parameter.name
+			spec: containers: [{
+				name: parameter.name
+				image: parameter.image
+			}]
+		}
+	}
+}
+
+outputs: {
+	service: {
+		apiVersion: "v1"
+		kind: "Service"
+		metadata: name: parameter.name + "-svc"
+		spec: {
+			selector: app: parameter.name
+			ports: [{
+				port: 80
+				targetPort: 8080
+			}]
+		}
+	}
+	configmap: {
+		apiVersion: "v1"
+		kind: "ConfigMap"
+		metadata: name: parameter.name + "-config"
+		data: {
+			config: "some-config"
+		}
+	}
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, componentDef)
+			Expect(err).NotTo(HaveOccurred())
+
+			// Clean up
+			Expect(k8sClient.Delete(ctx, componentDef)).Should(Succeed())
+		})
+
+		It("Should reject TraitDefinition with non-existent CRD in outputs", func() {
+			traitDef := &v1beta1.TraitDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "TraitDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-trait-with-invalid-output",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.TraitDefinitionSpec{
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	replicas: int
+}
+
+outputs: {
+	hpa: {
+		apiVersion: "autoscaling/v2"
+		kind: "HorizontalPodAutoscaler"
+		metadata: name: context.name + "-hpa"
+		spec: {
+			scaleTargetRef: {
+				apiVersion: "apps/v1"
+				kind: "Deployment"
+				name: context.name
+			}
+			minReplicas: parameter.replicas
+			maxReplicas: parameter.replicas * 2
+		}
+	}
+	customResource: {
+		apiVersion: "nonexistent.io/v1"
+		kind: "NonExistentCRD"
+		metadata: name: context.name + "-custom"
+		spec: {
+			data: "test"
+		}
+	}
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, traitDef)
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("resource type not found on cluster"))
+		})
+
+		It("Should accept TraitDefinition with valid resources", func() {
+			traitDef := &v1beta1.TraitDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "TraitDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-trait-with-valid-outputs",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.TraitDefinitionSpec{
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	replicas: int
+}
+
+patch: {
+	spec: replicas: parameter.replicas
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, traitDef)
+			Expect(err).NotTo(HaveOccurred())
+
+			// Clean up
+			Expect(k8sClient.Delete(ctx, traitDef)).Should(Succeed())
+		})
+
+		It("Should reject PolicyDefinition with non-existent CRD in outputs", func() {
+			policyDef := &v1beta1.PolicyDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "PolicyDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-policy-with-invalid-output",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.PolicyDefinitionSpec{
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	namespace: string
+}
+
+output: {
+	apiVersion: "v1"
+	kind: "Namespace"
+	metadata: name: parameter.namespace
+}
+
+outputs: {
+	invalidResource: {
+		apiVersion: "custom.io/v1beta1"
+		kind: "CustomPolicy"
+		metadata: name: parameter.namespace + "-policy"
+		spec: {
+			enabled: true
+		}
+	}
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, policyDef)
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("resource type not found on cluster"))
+		})
+
+		It("Should accept PolicyDefinition with valid resources", func() {
+			policyDef := &v1beta1.PolicyDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "PolicyDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-policy-with-valid-output",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.PolicyDefinitionSpec{
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	namespace: string
+}
+
+output: {
+	apiVersion: "v1"
+	kind: "Namespace"
+	metadata: name: parameter.namespace
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, policyDef)
+			Expect(err).NotTo(HaveOccurred())
+
+			// Clean up
+			Expect(k8sClient.Delete(ctx, policyDef)).Should(Succeed())
+		})
+
+		It("Should handle definitions with mixed valid and non-K8s resources", func() {
+			componentDef := &v1beta1.ComponentDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "ComponentDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-comp-with-mixed-outputs",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.ComponentDefinitionSpec{
+					Workload: common.WorkloadTypeDescriptor{
+						Definition: common.WorkloadGVK{
+							APIVersion: "apps/v1",
+							Kind:       "Deployment",
+						},
+					},
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	name: string
+	image: string
+}
+
+output: {
+	apiVersion: "apps/v1"
+	kind: "Deployment"
+	metadata: name: parameter.name
+	spec: {
+		selector: matchLabels: app: parameter.name
+		template: {
+			metadata: labels: app: parameter.name
+			spec: containers: [{
+				name: parameter.name
+				image: parameter.image
+			}]
+		}
+	}
+}
+
+outputs: {
+	service: {
+		apiVersion: "v1"
+		kind: "Service"
+		metadata: name: parameter.name + "-svc"
+		spec: {
+			selector: app: parameter.name
+			ports: [{port: 80}]
+		}
+	}
+	customData: {
+		field1: "value1"
+		field2: "value2"
+		nested: {
+			data: "some-data"
+		}
+	}
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, componentDef)
+			Expect(err).NotTo(HaveOccurred())
+
+			// Clean up
+			Expect(k8sClient.Delete(ctx, componentDef)).Should(Succeed())
+		})
+
+		It("Should handle update operations with validation", func() {
+			// First create a valid definition
+			componentDef := &v1beta1.ComponentDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "ComponentDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-comp-update-validation",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.ComponentDefinitionSpec{
+					Workload: common.WorkloadTypeDescriptor{
+						Definition: common.WorkloadGVK{
+							APIVersion: "apps/v1",
+							Kind:       "Deployment",
+						},
+					},
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	name: string
+}
+
+output: {
+	apiVersion: "apps/v1"
+	kind: "Deployment"
+	metadata: name: parameter.name
+	spec: {
+		selector: matchLabels: app: parameter.name
+		template: {
+			metadata: labels: app: parameter.name
+			spec: containers: [{
+				name: "app"
+				image: "nginx"
+			}]
+		}
+	}
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, componentDef)
+			Expect(err).NotTo(HaveOccurred())
+
+			// Try to update with invalid output
+			Eventually(func() error {
+				var existing v1beta1.ComponentDefinition
+				if err := k8sClient.Get(ctx, client.ObjectKey{Name: componentDef.Name, Namespace: namespace}, &existing); err != nil {
+					return err
+				}
+				existing.Spec.Schematic.CUE.Template = `
+parameter: {
+	name: string
+}
+
+output: {
+	apiVersion: "apps/v1"
+	kind: "Deployment"
+	metadata: name: parameter.name
+	spec: {
+		selector: matchLabels: app: parameter.name
+		template: {
+			metadata: labels: app: parameter.name
+			spec: containers: [{
+				name: "app"
+				image: "nginx"
+			}]
+		}
+	}
+}
+
+outputs: {
+	invalidCRD: {
+		apiVersion: "nonexistent.io/v1"
+		kind: "NonExistentResource"
+		metadata: name: parameter.name
+		spec: {}
+	}
+}`
+				return k8sClient.Update(ctx, &existing)
+			}, time.Second*5, time.Millisecond*500).Should(HaveOccurred())
+
+			// Clean up
+			Expect(k8sClient.Delete(ctx, componentDef)).Should(Succeed())
+		})
+
+		It("Should reject ComponentDefinition with multiple invalid CRDs", func() {
+			componentDef := &v1beta1.ComponentDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "ComponentDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-comp-multiple-invalid",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.ComponentDefinitionSpec{
+					Workload: common.WorkloadTypeDescriptor{
+						Definition: common.WorkloadGVK{
+							APIVersion: "apps/v1",
+							Kind:       "Deployment",
+						},
+					},
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	name: string
+	image: string
+}
+
+output: {
+	apiVersion: "apps/v1"
+	kind: "Deployment"
+	metadata: name: parameter.name
+	spec: {
+		selector: matchLabels: app: parameter.name
+		template: {
+			metadata: labels: app: parameter.name
+			spec: containers: [{
+				name: parameter.name
+				image: parameter.image
+			}]
+		}
+	}
+}
+
+outputs: {
+	validService: {
+		apiVersion: "v1"
+		kind: "Service"
+		metadata: name: parameter.name + "-svc"
+		spec: {
+			selector: app: parameter.name
+			ports: [{port: 80}]
+		}
+	}
+	invalidCRD1: {
+		apiVersion: "custom.io/v1alpha1"
+		kind: "CustomResource"
+		metadata: name: parameter.name + "-custom1"
+		spec: {foo: "bar"}
+	}
+	invalidCRD2: {
+		apiVersion: "another.io/v1beta1"
+		kind: "AnotherResource"
+		metadata: name: parameter.name + "-custom2"
+		spec: {enabled: true}
+	}
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, componentDef)
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("resource type not found on cluster"))
+		})
+
+		It("Should handle definitions with complex CUE expressions", func() {
+			componentDef := &v1beta1.ComponentDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "ComponentDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-comp-cue-expressions",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.ComponentDefinitionSpec{
+					Workload: common.WorkloadTypeDescriptor{
+						Definition: common.WorkloadGVK{
+							APIVersion: "apps/v1",
+							Kind:       "Deployment",
+						},
+					},
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+import "strings"
+
+parameter: {
+	name: string
+	image: string
+	enableService: bool | *false
+}
+
+output: {
+	apiVersion: "apps/v1"
+	kind: "Deployment"
+	metadata: {
+		name: strings.ToLower(parameter.name)
+		labels: {
+			app: parameter.name
+			version: "v1"
+		}
+	}
+	spec: {
+		selector: matchLabels: app: parameter.name
+		template: {
+			metadata: labels: app: parameter.name
+			spec: containers: [{
+				name: parameter.name
+				image: parameter.image
+			}]
+		}
+	}
+}
+
+if parameter.enableService {
+	outputs: service: {
+		apiVersion: "v1"
+		kind: "Service"
+		metadata: name: parameter.name + "-svc"
+		spec: {
+			selector: app: parameter.name
+			ports: [{port: 80, targetPort: 8080}]
+		}
+	}
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, componentDef)
+			Expect(err).NotTo(HaveOccurred())
+
+			// Clean up
+			Expect(k8sClient.Delete(ctx, componentDef)).Should(Succeed())
+		})
+
+		It("Should reject TraitDefinition with mixed valid and invalid outputs", func() {
+			traitDef := &v1beta1.TraitDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "TraitDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-trait-mixed-validity",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.TraitDefinitionSpec{
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	replicas: int | *2
+	cpu: string | *"100m"
+	memory: string | *"128Mi"
+}
+
+outputs: {
+	// Valid resources
+	service: {
+		apiVersion: "v1"
+		kind: "Service"
+		metadata: name: context.name + "-svc"
+		spec: {
+			selector: app: context.name
+			ports: [{
+				port: 80
+				targetPort: 8080
+			}]
+		}
+	}
+	configmap: {
+		apiVersion: "v1"
+		kind: "ConfigMap"
+		metadata: name: context.name + "-config"
+		data: {
+			cpu: parameter.cpu
+			memory: parameter.memory
+		}
+	}
+	// Invalid CRD - should cause failure
+	customMonitor: {
+		apiVersion: "monitoring.custom.io/v1alpha1"
+		kind: "ServiceMonitor"
+		metadata: name: context.name + "-monitor"
+		spec: {
+			selector: {
+				matchLabels: {
+					app: context.name
+				}
+			}
+			endpoints: [{
+				port: "metrics"
+				interval: "30s"
+			}]
+		}
+	}
+	// Non-K8s object (should be ignored)
+	metadata: {
+		version: "1.0.0"
+		features: ["monitoring", "scaling"]
+	}
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, traitDef)
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("resource type not found on cluster"))
+		})
+
+		It("Should accept PolicyDefinition with only standard Kubernetes resources", func() {
+			policyDef := &v1beta1.PolicyDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "PolicyDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-policy-standard-resources",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.PolicyDefinitionSpec{
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	namespace: string
+	enabled: bool | *true
+	labels: {...} | *{}
+}
+
+output: {
+	apiVersion: "v1"
+	kind: "Namespace"
+	metadata: {
+		name: parameter.namespace
+		labels: parameter.labels
+	}
+}
+
+outputs: {
+	networkPolicy: {
+		apiVersion: "networking.k8s.io/v1"
+		kind: "NetworkPolicy"
+		metadata: {
+			name: parameter.namespace + "-default-deny"
+			namespace: parameter.namespace
+		}
+		spec: {
+			podSelector: {}
+			policyTypes: ["Ingress", "Egress"]
+		}
+	}
+	resourceQuota: {
+		apiVersion: "v1"
+		kind: "ResourceQuota"
+		metadata: {
+			name: parameter.namespace + "-quota"
+			namespace: parameter.namespace
+		}
+		spec: {
+			hard: {
+				"requests.cpu": "4"
+				"requests.memory": "8Gi"
+				"limits.cpu": "8"
+				"limits.memory": "16Gi"
+				"pods": "10"
+			}
+		}
+	}
+	limitRange: {
+		apiVersion: "v1"
+		kind: "LimitRange"
+		metadata: {
+			name: parameter.namespace + "-limits"
+			namespace: parameter.namespace
+		}
+		spec: {
+			limits: [{
+				type: "Container"
+				default: {
+					cpu: "100m"
+					memory: "128Mi"
+				}
+				defaultRequest: {
+					cpu: "50m"
+					memory: "64Mi"
+				}
+			}]
+		}
+	}
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, policyDef)
+			Expect(err).NotTo(HaveOccurred())
+
+			// Clean up
+			Expect(k8sClient.Delete(ctx, policyDef)).Should(Succeed())
+		})
+
+		It("Should validate definitions with empty outputs", func() {
+			componentDef := &v1beta1.ComponentDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "ComponentDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-comp-empty-outputs",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.ComponentDefinitionSpec{
+					Workload: common.WorkloadTypeDescriptor{
+						Definition: common.WorkloadGVK{
+							APIVersion: "apps/v1",
+							Kind:       "Deployment",
+						},
+					},
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	name: string
+	image: string
+}
+
+output: {
+	apiVersion: "apps/v1"
+	kind: "Deployment"
+	metadata: name: parameter.name
+	spec: {
+		selector: matchLabels: app: parameter.name
+		template: {
+			metadata: labels: app: parameter.name
+			spec: containers: [{
+				name: parameter.name
+				image: parameter.image
+			}]
+		}
+	}
+}
+
+// Empty outputs should be valid
+outputs: {}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, componentDef)
+			Expect(err).NotTo(HaveOccurred())
+
+			// Clean up
+			Expect(k8sClient.Delete(ctx, componentDef)).Should(Succeed())
+		})
+
+		It("Should reject WorkflowStepDefinition with non-existent CRD in outputs", func() {
+			workflowStepDef := &v1beta1.WorkflowStepDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "WorkflowStepDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-workflowstep-with-invalid-output",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.WorkflowStepDefinitionSpec{
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	namespace: string
+	name: string
+}
+
+output: {
+	apiVersion: "v1"
+	kind: "ConfigMap"
+	metadata: {
+		name: parameter.name
+		namespace: parameter.namespace
+	}
+	data: {
+		status: "created"
+	}
+}
+
+outputs: {
+	invalidStep: {
+		apiVersion: "workflow.custom.io/v1alpha1"
+		kind: "WorkflowStep"
+		metadata: name: parameter.name + "-step"
+		spec: {
+			type: "custom"
+			enabled: true
+		}
+	}
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, workflowStepDef)
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("resource type not found on cluster"))
+		})
+
+		It("Should accept WorkflowStepDefinition with valid resources", func() {
+			workflowStepDef := &v1beta1.WorkflowStepDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "WorkflowStepDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-workflowstep-with-valid-outputs",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.WorkflowStepDefinitionSpec{
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	namespace: string
+	name: string
+	data: {...}
+}
+
+output: {
+	apiVersion: "v1"
+	kind: "ConfigMap"
+	metadata: {
+		name: parameter.name
+		namespace: parameter.namespace
+	}
+	data: parameter.data
+}
+
+outputs: {
+	secret: {
+		apiVersion: "v1"
+		kind: "Secret"
+		metadata: {
+			name: parameter.name + "-secret"
+			namespace: parameter.namespace
+		}
+		type: "Opaque"
+		data: {
+			key: "dmFsdWU="  // base64 encoded "value"
+		}
+	}
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, workflowStepDef)
+			Expect(err).NotTo(HaveOccurred())
+
+			// Clean up
+			Expect(k8sClient.Delete(ctx, workflowStepDef)).Should(Succeed())
+		})
+
+		It("Should reject WorkflowStepDefinition with mixed valid and invalid resources", func() {
+			workflowStepDef := &v1beta1.WorkflowStepDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "WorkflowStepDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-workflowstep-mixed-outputs",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.WorkflowStepDefinitionSpec{
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	name: string
+	namespace: string
+}
+
+output: {
+	apiVersion: "batch/v1"
+	kind: "Job"
+	metadata: {
+		name: parameter.name
+		namespace: parameter.namespace
+	}
+	spec: {
+		template: {
+			spec: {
+				containers: [{
+					name: "job"
+					image: "busybox"
+					command: ["echo", "hello"]
+				}]
+				restartPolicy: "Never"
+			}
+		}
+	}
+}
+
+outputs: {
+	// Valid resource
+	cronJob: {
+		apiVersion: "batch/v1"
+		kind: "CronJob"
+		metadata: {
+			name: parameter.name + "-cron"
+			namespace: parameter.namespace
+		}
+		spec: {
+			schedule: "*/5 * * * *"
+			jobTemplate: {
+				spec: {
+					template: {
+						spec: {
+							containers: [{
+								name: "cron"
+								image: "busybox"
+								command: ["echo", "cron"]
+							}]
+							restartPolicy: "Never"
+						}
+					}
+				}
+			}
+		}
+	}
+	// Invalid custom resource
+	customScheduler: {
+		apiVersion: "scheduler.custom.io/v1beta1"
+		kind: "CustomScheduler"
+		metadata: {
+			name: parameter.name + "-scheduler"
+		}
+		spec: {
+			interval: "5m"
+			action: "notify"
+		}
+	}
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, workflowStepDef)
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("resource type not found on cluster"))
+		})
+
+		It("Should accept WorkflowStepDefinition without outputs", func() {
+			workflowStepDef := &v1beta1.WorkflowStepDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "WorkflowStepDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-workflowstep-no-outputs",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.WorkflowStepDefinitionSpec{
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	message: string
+	delay: int | *1
+}
+
+// This workflow step just processes data without creating resources
+processedData: {
+	message: parameter.message
+	timestamp: "2024-01-01T00:00:00Z"
+	processed: true
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, workflowStepDef)
+			Expect(err).NotTo(HaveOccurred())
+
+			// Clean up
+			Expect(k8sClient.Delete(ctx, workflowStepDef)).Should(Succeed())
+		})
+
+		It("Should reject ComponentDefinition with invalid apiVersion format", func() {
+			componentDef := &v1beta1.ComponentDefinition{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "ComponentDefinition",
+					APIVersion: "core.oam.dev/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-comp-invalid-apiversion",
+					Namespace: namespace,
+				},
+				Spec: v1beta1.ComponentDefinitionSpec{
+					Workload: common.WorkloadTypeDescriptor{
+						Definition: common.WorkloadGVK{
+							APIVersion: "apps/v1",
+							Kind:       "Deployment",
+						},
+					},
+					Schematic: &common.Schematic{
+						CUE: &common.CUE{
+							Template: `
+parameter: {
+	name: string
+}
+
+output: {
+	apiVersion: "apps/v1"
+	kind: "Deployment"
+	metadata: name: parameter.name
+	spec: {
+		selector: matchLabels: app: parameter.name
+		template: {
+			metadata: labels: app: parameter.name
+			spec: containers: [{
+				name: "nginx"
+				image: "nginx:latest"
+			}]
+		}
+	}
+}
+
+outputs: {
+	invalidResource: {
+		apiVersion: "invalid-format-no-slash"
+		kind: "SomeResource"
+		metadata: name: parameter.name
+		spec: {}
+	}
+}`,
+						},
+					},
+				},
+			}
+
+			err := k8sClient.Create(ctx, componentDef)
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("resource type not found on cluster"))
+		})
+	})
+})
diff --git a/test/e2e-test/definition_revision_test.go b/test/e2e-test/definition_revision_test.go
index 63db5e17..b437011e 100644
--- a/test/e2e-test/definition_revision_test.go
+++ b/test/e2e-test/definition_revision_test.go
@@ -930,7 +930,7 @@ outputs: service: {
 }
 
 outputs: ingress: {
-	apiVersion: "networking.k8s.io/v1beta1"
+	apiVersion: "networking.k8s.io/v1"
 	kind:       "Ingress"
 	metadata:
 		name: context.name
@@ -941,9 +941,14 @@ outputs: ingress: {
 				paths: [
 					for k, v in parameter.http {
 						path: k
+						pathType: "Prefix"
 						backend: {
-							serviceName: context.name
-							servicePort: v
+							service: {
+								name: context.name
+								port: {
+									number: v
+								}
+							}
 						}
 					},
 				]