diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 82aa4ccb2..8d809172f 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -5,6 +5,7 @@ on: push: branches: - main + - 'release/**' paths: - 'packages/*/pyproject.toml' - '!packages/*/samples/**/pyproject.toml' @@ -20,8 +21,8 @@ jobs: detect-publishable-packages: runs-on: ubuntu-latest outputs: - packages: ${{ steps.detect.outputs.packages }} - count: ${{ steps.detect.outputs.count }} + packages: ${{ steps.detect.outputs.packages || '[]' }} + count: ${{ steps.detect.outputs.count || '0' }} steps: - name: Checkout uses: actions/checkout@v4 @@ -33,6 +34,7 @@ jobs: - name: Detect publishable packages id: detect + if: ${{ !github.event.created }} run: python .github/scripts/detect_publishable_packages.py # --- Tier 0: uipath-core (no internal dependencies) --- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e05e9c73..f63adfbcb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - main + - 'release/**' permissions: contents: read @@ -18,6 +19,8 @@ jobs: test: uses: ./.github/workflows/test-packages.yml + secrets: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} check-versions: uses: ./.github/workflows/check-version-availability.yml diff --git a/.github/workflows/test-packages.yml b/.github/workflows/test-packages.yml index 58e37a42a..e51109260 100644 --- a/.github/workflows/test-packages.yml +++ b/.github/workflows/test-packages.yml @@ -2,6 +2,9 @@ name: Test Packages on: workflow_call: + secrets: + SONAR_TOKEN: + required: false permissions: contents: read @@ -176,12 +179,53 @@ jobs: run: uv sync --all-extras --python ${{ matrix.python-version }} - name: Run tests - if: steps.check.outputs.skip != 'true' + if: steps.check.outputs.skip != 'true' && !(matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13') working-directory: packages/uipath run: uv run pytest + - name: Run tests with coverage + if: steps.check.outputs.skip != 'true' && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13' + working-directory: packages/uipath + run: uv run pytest --cov-report=xml + + - name: Upload coverage XML report + if: steps.check.outputs.skip != 'true' && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13' && always() + uses: actions/upload-artifact@v4 + with: + name: coverage-xml-uipath + path: packages/uipath/coverage.xml + retention-days: 30 + continue-on-error: true + sonarcloud: + name: SonarCloud + needs: [test-uipath-core, test-uipath-platform, test-uipath] + runs-on: ubuntu-latest + if: always() && needs.test-uipath-core.result != 'failure' && needs.test-uipath-platform.result != 'failure' && needs.test-uipath.result != 'failure' + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download uipath coverage + uses: actions/download-artifact@v4 + continue-on-error: true + with: + name: coverage-xml-uipath + path: packages/uipath + + - name: Rewrite coverage XML to repo-relative path + run: sed -i -E 's|.*packages/uipath/src\|src|packages/uipath/src|g' packages/uipath/coverage.xml || true + + - name: SonarCloud Scan + uses: SonarSource/sonarqube-scan-action@2f77a1ec69fb1d595b06f35ab27e97605bdef703 # v5 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + test-gate: name: Test needs: [test-uipath-core, test-uipath-platform, test-uipath] diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index d086b5ce6..ca5372502 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.10.37" +version = "2.10.37.post1" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath/src/uipath/_cli/_debug/_bridge.py b/packages/uipath/src/uipath/_cli/_debug/_bridge.py index 9607398a0..d9afe0e31 100644 --- a/packages/uipath/src/uipath/_cli/_debug/_bridge.py +++ b/packages/uipath/src/uipath/_cli/_debug/_bridge.py @@ -741,6 +741,9 @@ async def _handle_start(self, args: list[Any]) -> None: f"Debug started: breakpoints={self.state.breakpoints}, step_mode={step_mode}" ) + # handle race conditions, runtime connected to debug bridge before the receiver + await self.emit_execution_started() + async def _handle_resume(self, args: list[Any]) -> None: """Handle Resume command from SignalR server. diff --git a/packages/uipath/tests/cli/chat/test_bridge.py b/packages/uipath/tests/cli/chat/test_bridge.py index 2da4f31ad..96441b56f 100644 --- a/packages/uipath/tests/cli/chat/test_bridge.py +++ b/packages/uipath/tests/cli/chat/test_bridge.py @@ -351,3 +351,43 @@ async def test_send_with_datetime_does_not_raise(self) -> None: assert parsed_data["message"] == "test message" assert isinstance(parsed_data["timestamp"], str) assert isinstance(parsed_data["nested"]["created_at"], str) + + @pytest.mark.anyio + async def test_handle_start_emits_execution_started(self) -> None: + """Start command applies breakpoints and re-emits ExecutionStarted.""" + bridge = SignalRDebugBridge( + hub_url="wss://test.example.com/signalr", + access_token="test-token", + headers={}, + ) + + mock_client = MagicMock() + mock_client.send = AsyncMock() + bridge._client = mock_client + + await bridge._handle_start( + ['{"breakpoints": ["node-a", "node-b"], "enableStepMode": true}'] + ) + + assert bridge.state.breakpoints == {"node-a", "node-b"} + assert bridge.state.step_mode is True + mock_client.send.assert_awaited_once() + assert mock_client.send.call_args.kwargs["arguments"][0] == ( + "OnExecutionStarted" + ) + + @pytest.mark.anyio + async def test_handle_start_with_empty_args_does_not_emit(self) -> None: + bridge = SignalRDebugBridge( + hub_url="wss://test.example.com/signalr", + access_token="test-token", + headers={}, + ) + + mock_client = MagicMock() + mock_client.send = AsyncMock() + bridge._client = mock_client + + await bridge._handle_start([]) + + mock_client.send.assert_not_awaited() diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index c8ec1eafc..b07fe1e1f 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2543,7 +2543,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.10.37" +version = "2.10.37.post1" source = { editable = "." } dependencies = [ { name = "applicationinsights" }, diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 000000000..9aa72ff12 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,13 @@ +sonar.projectKey=UiPath_uipath-python +sonar.organization=ui +sonar.host.url=https://sonarcloud.io + +sonar.sources=packages/uipath/src,packages/uipath-core/src,packages/uipath-platform/src +sonar.tests=packages/uipath/tests,packages/uipath-core/tests,packages/uipath-platform/tests + +sonar.python.version=3.11,3.12,3.13 +sonar.python.coverage.reportPaths=packages/uipath/coverage.xml + +sonar.exclusions=**/samples/**,**/testcases/**,**/template/**,**/_resources/** + +sonar.sourceEncoding=UTF-8