diff --git a/evaluation/integration_tests/tests/t08_token_authentication.py b/evaluation/integration_tests/tests/t08_token_authentication.py new file mode 100644 index 000000000000..9e927352e92d --- /dev/null +++ b/evaluation/integration_tests/tests/t08_token_authentication.py @@ -0,0 +1,258 @@ +import os +import pytest +from unittest.mock import patch, MagicMock +import tempfile +import shutil +import json +from pathlib import Path + +from openhands.integrations.provider import ProviderToken, ProviderType +from openhands.events.stream import EventStream +from openhands.storage import get_file_store + + +class TestTokenAuthentication: + """Integration tests for token authentication in workflows""" + + @pytest.fixture + def temp_dir(self): + """Create a temporary directory for testing""" + temp_dir = tempfile.mkdtemp() + yield temp_dir + shutil.rmtree(temp_dir) + + @pytest.fixture + def workflow_dir(self, temp_dir): + """Create a directory with workflow files for testing""" + workflow_dir = os.path.join(temp_dir, '.github', 'workflows') + os.makedirs(workflow_dir, exist_ok=True) + return workflow_dir + + @pytest.fixture + def github_token(self): + """Create a minimal permission GitHub token for testing""" + return ProviderToken(token="github_test_token") + + @pytest.fixture + def github_pat(self): + """Create a minimal permission GitHub PAT for testing""" + return ProviderToken(token="github_pat_test_token") + + @pytest.fixture + def gitlab_token(self): + """Create a minimal permission GitLab token for testing""" + return ProviderToken(token="gitlab_test_token") + + def test_github_token_authentication(self, workflow_dir, github_token): + """Test GitHub token authentication in workflows""" + workflow_path = os.path.join(workflow_dir, 'github_token_workflow.yml') + with open(workflow_path, 'w') as f: + f.write(""" +name: GitHub Token Workflow +on: + workflow_dispatch: +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Test GitHub Token + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Testing GitHub token" + gh repo list +""") + + mock_github_client = MagicMock() + mock_github_client.get_repo.return_value = {"name": "test-repo", "full_name": "owner/test-repo"} + + with patch('os.environ', {"GITHUB_TOKEN": str(github_token.token)}), \ + patch('openhands.integrations.github.create_github_client', return_value=mock_github_client): + + + assert os.environ.get("GITHUB_TOKEN") == "github_test_token" + + from openhands.integrations.github import create_github_client + client = create_github_client() + assert client == mock_github_client + + repo = client.get_repo("owner/test-repo") + assert repo["name"] == "test-repo" + + def test_github_pat_authentication(self, workflow_dir, github_pat): + """Test GitHub PAT authentication in workflows""" + workflow_path = os.path.join(workflow_dir, 'github_pat_workflow.yml') + with open(workflow_path, 'w') as f: + f.write(""" +name: GitHub PAT Workflow +on: + workflow_dispatch: +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Test GitHub PAT + env: + PAT_TOKEN: ${{ secrets.PAT_TOKEN }} + run: | + echo "Testing GitHub PAT" + gh repo list +""") + + mock_github_client = MagicMock() + mock_github_client.get_repo.return_value = {"name": "test-repo", "full_name": "owner/test-repo"} + + with patch('os.environ', {"PAT_TOKEN": str(github_pat.token)}), \ + patch('openhands.integrations.github.create_github_client', return_value=mock_github_client): + + + assert os.environ.get("PAT_TOKEN") == "github_pat_test_token" + + from openhands.integrations.github import create_github_client + client = create_github_client() + assert client == mock_github_client + + repo = client.get_repo("owner/test-repo") + assert repo["name"] == "test-repo" + + def test_gitlab_token_authentication(self, workflow_dir, gitlab_token): + """Test GitLab token authentication in workflows""" + workflow_path = os.path.join(workflow_dir, 'gitlab_token_workflow.yml') + with open(workflow_path, 'w') as f: + f.write(""" +name: GitLab Token Workflow +on: + workflow_dispatch: +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Test GitLab Token + env: + GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} + run: | + echo "Testing GitLab token" + curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" "https://gitlab.com/api/v4/projects" +""") + + mock_gitlab_client = MagicMock() + mock_gitlab_client.get_project.return_value = {"id": 123, "name": "test-project"} + + with patch('os.environ', {"GITLAB_TOKEN": str(gitlab_token.token)}), \ + patch('openhands.integrations.gitlab.create_gitlab_client', return_value=mock_gitlab_client): + + + assert os.environ.get("GITLAB_TOKEN") == "gitlab_test_token" + + from openhands.integrations.gitlab import create_gitlab_client + client = create_gitlab_client() + assert client == mock_gitlab_client + + project = client.get_project(123) + assert project["name"] == "test-project" + + def test_token_validation_in_workflow(self, workflow_dir): + """Test token validation in workflow files""" + workflow_path = os.path.join(workflow_dir, 'multi_token_workflow.yml') + with open(workflow_path, 'w') as f: + f.write(""" +name: Multi-Token Workflow +on: + workflow_dispatch: +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Test GitHub Token + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: echo "Testing GitHub token" + - name: Test GitHub PAT + env: + PAT_TOKEN: ${{ secrets.PAT_TOKEN }} + run: echo "Testing GitHub PAT" + - name: Test GitLab Token + env: + GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} + run: echo "Testing GitLab token" +""") + + def validate_workflow_tokens(workflow_path): + """Validate tokens used in a workflow file""" + with open(workflow_path, 'r') as f: + content = f.read() + + token_refs = [] + if "${{ secrets.GITHUB_TOKEN }}" in content: + token_refs.append("GITHUB_TOKEN") + if "${{ secrets.PAT_TOKEN }}" in content: + token_refs.append("PAT_TOKEN") + if "${{ secrets.GITLAB_TOKEN }}" in content: + token_refs.append("GITLAB_TOKEN") + + return token_refs + + token_refs = validate_workflow_tokens(workflow_path) + + assert "GITHUB_TOKEN" in token_refs + assert "PAT_TOKEN" in token_refs + assert "GITLAB_TOKEN" in token_refs + + assert len(token_refs) == 3 + + def test_token_permissions(self, workflow_dir): + """Test token permissions in workflows""" + workflow_path = os.path.join(workflow_dir, 'permissions_workflow.yml') + with open(workflow_path, 'w') as f: + f.write(""" +name: Permissions Workflow +on: + workflow_dispatch: +permissions: + contents: read + issues: write +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Test Permissions + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Testing token permissions" + gh issue create --title "Test Issue" --body "Created from workflow" +""") + + def validate_workflow_permissions(workflow_path): + """Validate permissions defined in a workflow file""" + with open(workflow_path, 'r') as f: + content = f.read() + + permissions = {} + if "permissions:" in content: + lines = content.split("\n") + in_permissions = False + for line in lines: + if line.strip() == "permissions:": + in_permissions = True + elif in_permissions and ":" in line and not line.startswith("jobs:"): + key, value = line.strip().split(":", 1) + permissions[key.strip()] = value.strip() + elif in_permissions and (line.startswith("jobs:") or not line.strip()): + in_permissions = False + + return permissions + + permissions = validate_workflow_permissions(workflow_path) + + assert "contents" in permissions + assert permissions["contents"] == "read" + assert "issues" in permissions + assert permissions["issues"] == "write" + + assert len(permissions) == 2 diff --git a/tests/unit/resolver/github/test_token_handling.py b/tests/unit/resolver/github/test_token_handling.py new file mode 100644 index 000000000000..51c5126fe2c4 --- /dev/null +++ b/tests/unit/resolver/github/test_token_handling.py @@ -0,0 +1,154 @@ +import os +import pytest +from unittest.mock import patch, MagicMock +from pydantic import SecretStr + +from openhands.integrations.provider import ProviderToken, ProviderType +from openhands.events.stream import EventStream +from openhands.storage import get_file_store + + +class TestWorkflowTokenHandling: + """Tests for token handling in GitHub workflows""" + + @pytest.fixture + def temp_dir(self, tmp_path_factory: pytest.TempPathFactory) -> str: + return str(tmp_path_factory.mktemp('test_workflow_tokens')) + + @pytest.fixture + def event_stream(self, temp_dir): + file_store = get_file_store('local', temp_dir) + return EventStream('test_workflow', file_store) + + @pytest.fixture + def github_token(self): + return ProviderToken(token=SecretStr('github_test_token')) + + @pytest.fixture + def github_pat(self): + return ProviderToken(token=SecretStr('github_pat_test_token')) + + @pytest.fixture + def gitlab_token(self): + return ProviderToken(token=SecretStr('gitlab_test_token')) + + @pytest.mark.parametrize( + "token_type,token_env_var,expected_value", + [ + (ProviderType.GITHUB, "GITHUB_TOKEN", "github_test_token"), + (ProviderType.GITHUB, "PAT_TOKEN", "github_pat_test_token"), + (ProviderType.GITLAB, "GITLAB_TOKEN", "gitlab_test_token"), + ], + ) + def test_workflow_token_validation( + self, token_type, token_env_var, expected_value, event_stream, github_token, github_pat, gitlab_token + ): + """Test that workflow tokens are correctly validated and exported""" + provider_tokens = {} + if token_type == ProviderType.GITHUB and token_env_var == "GITHUB_TOKEN": + provider_tokens = {ProviderType.GITHUB: github_token} + elif token_type == ProviderType.GITHUB and token_env_var == "PAT_TOKEN": + provider_tokens = {ProviderType.GITHUB: github_pat} + elif token_type == ProviderType.GITLAB: + provider_tokens = {ProviderType.GITLAB: gitlab_token} + + workflow_content = f""" + name: Test Workflow + on: + workflow_call: + secrets: + {token_env_var}: + required: true + jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Test Step + env: + TOKEN: ${{{token_env_var}}} + run: echo "Using token for authentication" + """ + + mock_parser = MagicMock() + mock_parser.extract_token_references.return_value = [token_env_var] + + with patch('os.environ', {token_env_var: expected_value}): + assert os.environ.get(token_env_var) == expected_value + + token_refs = mock_parser.extract_token_references(workflow_content) + assert token_env_var in token_refs + + for ref in token_refs: + if ref == token_env_var: + assert os.environ.get(ref) == expected_value + + def test_workflow_token_missing(self, event_stream): + """Test handling of missing workflow tokens""" + workflow_content = """ + name: Test Workflow + on: + workflow_call: + secrets: + GITHUB_TOKEN: + required: true + jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Test Step + env: + TOKEN: ${{GITHUB_TOKEN}} + run: echo "Using token for authentication" + """ + + mock_parser = MagicMock() + mock_parser.extract_token_references.return_value = ["GITHUB_TOKEN"] + + with patch('os.environ', {}): + token_refs = mock_parser.extract_token_references(workflow_content) + assert "GITHUB_TOKEN" in token_refs + + for ref in token_refs: + assert os.environ.get(ref) is None + + def test_workflow_multiple_tokens(self, event_stream, github_token, gitlab_token): + """Test handling of multiple tokens in a workflow""" + workflow_content = """ + name: Test Workflow + on: + workflow_call: + secrets: + GITHUB_TOKEN: + required: true + GITLAB_TOKEN: + required: true + jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Test GitHub + env: + TOKEN: ${{GITHUB_TOKEN}} + run: echo "Using GitHub token" + - name: Test GitLab + env: + TOKEN: ${{GITLAB_TOKEN}} + run: echo "Using GitLab token" + """ + + mock_parser = MagicMock() + mock_parser.extract_token_references.return_value = ["GITHUB_TOKEN", "GITLAB_TOKEN"] + + with patch('os.environ', { + "GITHUB_TOKEN": "github_test_token", + "GITLAB_TOKEN": "gitlab_test_token" + }): + token_refs = mock_parser.extract_token_references(workflow_content) + assert "GITHUB_TOKEN" in token_refs + assert "GITLAB_TOKEN" in token_refs + + assert os.environ.get("GITHUB_TOKEN") == "github_test_token" + assert os.environ.get("GITLAB_TOKEN") == "gitlab_test_token" diff --git a/tests/unit/resolver/test_api_interactions.py b/tests/unit/resolver/test_api_interactions.py new file mode 100644 index 000000000000..d5d3f00fb9cd --- /dev/null +++ b/tests/unit/resolver/test_api_interactions.py @@ -0,0 +1,232 @@ +import pytest +from unittest.mock import patch, MagicMock +from pydantic import SecretStr + +from openhands.integrations.provider import ProviderToken, ProviderType + + +class MockGitHubAPI: + """Mock implementation of GitHub API for testing""" + + def __init__(self, token=None): + self.token = token + self.authenticated = token is not None + self.requests = [] + + def create_issue(self, repo, title, body): + """Mock creating an issue""" + if not self.authenticated: + raise Exception("Authentication required") + + self.requests.append({ + "type": "create_issue", + "repo": repo, + "title": title, + "body": body + }) + return {"id": 12345, "number": 1, "title": title} + + def create_pull_request(self, repo, title, body, head, base): + """Mock creating a pull request""" + if not self.authenticated: + raise Exception("Authentication required") + + self.requests.append({ + "type": "create_pull_request", + "repo": repo, + "title": title, + "body": body, + "head": head, + "base": base + }) + return {"id": 67890, "number": 2, "title": title} + + +class MockGitLabAPI: + """Mock implementation of GitLab API for testing""" + + def __init__(self, token=None): + self.token = token + self.authenticated = token is not None + self.requests = [] + + def create_issue(self, project_id, title, description): + """Mock creating an issue""" + if not self.authenticated: + raise Exception("Authentication required") + + self.requests.append({ + "type": "create_issue", + "project_id": project_id, + "title": title, + "description": description + }) + return {"id": 12345, "iid": 1, "title": title} + + def create_merge_request(self, project_id, title, description, source_branch, target_branch): + """Mock creating a merge request""" + if not self.authenticated: + raise Exception("Authentication required") + + self.requests.append({ + "type": "create_merge_request", + "project_id": project_id, + "title": title, + "description": description, + "source_branch": source_branch, + "target_branch": target_branch + }) + return {"id": 67890, "iid": 2, "title": title} + + +class TestAPIInteractions: + """Tests for API interactions with different token types""" + + @pytest.fixture + def github_api(self): + """Fixture for GitHub API mock""" + return MockGitHubAPI(token="github_test_token") + + @pytest.fixture + def gitlab_api(self): + """Fixture for GitLab API mock""" + return MockGitLabAPI(token="gitlab_test_token") + + @pytest.fixture + def github_token(self): + """Fixture for GitHub token""" + return ProviderToken(token=SecretStr("github_test_token")) + + @pytest.fixture + def github_pat(self): + """Fixture for GitHub PAT token""" + return ProviderToken(token=SecretStr("github_pat_test_token")) + + @pytest.fixture + def gitlab_token(self): + """Fixture for GitLab token""" + return ProviderToken(token=SecretStr("gitlab_test_token")) + + def test_github_token_api_interaction(self, github_api, github_token): + """Test GitHub API interactions with GitHub token""" + with patch("openhands.integrations.github.create_github_client", return_value=github_api): + result = github_api.create_issue( + repo="test/repo", + title="Test Issue", + body="This is a test issue" + ) + + assert result["number"] == 1 + assert result["title"] == "Test Issue" + + assert len(github_api.requests) == 1 + assert github_api.requests[0]["type"] == "create_issue" + assert github_api.requests[0]["repo"] == "test/repo" + + def test_github_pat_api_interaction(self, github_api, github_pat): + """Test GitHub API interactions with GitHub PAT token""" + github_api.token = str(github_pat.token.get_secret_value()) + + with patch("openhands.integrations.github.create_github_client", return_value=github_api): + result = github_api.create_pull_request( + repo="test/repo", + title="Test PR", + body="This is a test PR", + head="feature-branch", + base="main" + ) + + assert result["number"] == 2 + assert result["title"] == "Test PR" + + assert len(github_api.requests) == 1 + assert github_api.requests[0]["type"] == "create_pull_request" + assert github_api.requests[0]["repo"] == "test/repo" + assert github_api.requests[0]["head"] == "feature-branch" + assert github_api.requests[0]["base"] == "main" + + def test_gitlab_token_api_interaction(self, gitlab_api, gitlab_token): + """Test GitLab API interactions with GitLab token""" + with patch("openhands.integrations.gitlab.create_gitlab_client", return_value=gitlab_api): + result = gitlab_api.create_issue( + project_id=123, + title="Test GitLab Issue", + description="This is a test GitLab issue" + ) + + assert result["iid"] == 1 + assert result["title"] == "Test GitLab Issue" + + assert len(gitlab_api.requests) == 1 + assert gitlab_api.requests[0]["type"] == "create_issue" + assert gitlab_api.requests[0]["project_id"] == 123 + + def test_api_authentication_failure(self): + """Test API authentication failure handling""" + github_api = MockGitHubAPI() + gitlab_api = MockGitLabAPI() + + with pytest.raises(Exception, match="Authentication required"): + github_api.create_issue( + repo="test/repo", + title="Test Issue", + body="This is a test issue" + ) + + with pytest.raises(Exception, match="Authentication required"): + gitlab_api.create_issue( + project_id=123, + title="Test GitLab Issue", + description="This is a test GitLab issue" + ) + + def test_workflow_api_integration(self, github_api, gitlab_api): + """Test workflow integration with API clients""" + workflow_content = """ + name: Test Workflow + on: + workflow_call: + secrets: + GITHUB_TOKEN: + required: true + GITLAB_TOKEN: + required: true + jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Create GitHub Issue + env: + GH_TOKEN: ${{GITHUB_TOKEN}} + run: | + gh issue create --title "Test Issue" --body "Created from workflow" + - name: Create GitLab Issue + env: + GITLAB_TOKEN: ${{GITLAB_TOKEN}} + run: | + curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ + "https://gitlab.com/api/v4/projects/123/issues?title=Test&description=Created" + """ + + with patch("openhands.integrations.github.create_github_client", return_value=github_api), \ + patch("openhands.integrations.gitlab.create_gitlab_client", return_value=gitlab_api): + + + github_api.create_issue( + repo="test/repo", + title="Test Issue", + body="Created from workflow" + ) + + gitlab_api.create_issue( + project_id=123, + title="Test", + description="Created" + ) + + assert len(github_api.requests) == 1 + assert github_api.requests[0]["type"] == "create_issue" + + assert len(gitlab_api.requests) == 1 + assert gitlab_api.requests[0]["type"] == "create_issue" diff --git a/tests/workflow/test_local_workflows.py b/tests/workflow/test_local_workflows.py new file mode 100644 index 000000000000..d29bb5cfc7c8 --- /dev/null +++ b/tests/workflow/test_local_workflows.py @@ -0,0 +1,232 @@ +import os +import pytest +import subprocess +from unittest.mock import patch, MagicMock +import tempfile +import shutil + +from openhands.integrations.provider import ProviderToken, ProviderType + + +class TestLocalWorkflows: + """Tests for running workflows locally using act""" + + @pytest.fixture + def temp_workflow_dir(self): + """Create a temporary directory for workflow files""" + temp_dir = tempfile.mkdtemp() + yield temp_dir + shutil.rmtree(temp_dir) + + @pytest.fixture + def github_workflow_file(self, temp_workflow_dir): + """Create a test GitHub workflow file""" + workflow_dir = os.path.join(temp_workflow_dir, '.github', 'workflows') + os.makedirs(workflow_dir, exist_ok=True) + + workflow_path = os.path.join(workflow_dir, 'test_github_workflow.yml') + with open(workflow_path, 'w') as f: + f.write(""" +name: Test GitHub Workflow +on: + workflow_dispatch: +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Test GitHub Token + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Testing GitHub token" + if [ -z "$GITHUB_TOKEN" ]; then + echo "GitHub token is missing" + exit 1 + fi + echo "GitHub token is valid" +""") + return workflow_path + + @pytest.fixture + def gitlab_workflow_file(self, temp_workflow_dir): + """Create a test GitLab workflow file""" + workflow_dir = os.path.join(temp_workflow_dir, '.github', 'workflows') + os.makedirs(workflow_dir, exist_ok=True) + + workflow_path = os.path.join(workflow_dir, 'test_gitlab_workflow.yml') + with open(workflow_path, 'w') as f: + f.write(""" +name: Test GitLab Workflow +on: + workflow_dispatch: +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Test GitLab Token + env: + GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} + run: | + echo "Testing GitLab token" + if [ -z "$GITLAB_TOKEN" ]; then + echo "GitLab token is missing" + exit 1 + fi + echo "GitLab token is valid" +""") + return workflow_path + + @pytest.fixture + def pat_workflow_file(self, temp_workflow_dir): + """Create a test PAT workflow file""" + workflow_dir = os.path.join(temp_workflow_dir, '.github', 'workflows') + os.makedirs(workflow_dir, exist_ok=True) + + workflow_path = os.path.join(workflow_dir, 'test_pat_workflow.yml') + with open(workflow_path, 'w') as f: + f.write(""" +name: Test PAT Workflow +on: + workflow_dispatch: +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Test PAT Token + env: + PAT_TOKEN: ${{ secrets.PAT_TOKEN }} + run: | + echo "Testing PAT token" + if [ -z "$PAT_TOKEN" ]; then + echo "PAT token is missing" + exit 1 + fi + echo "PAT token is valid" +""") + return workflow_path + + def test_act_installed(self): + """Test that act is installed or can be installed""" + + with patch('subprocess.run') as mock_run: + mock_run.return_value = MagicMock(returncode=0, stdout=b'act version 0.2.30') + + result = subprocess.run(['act', '--version'], capture_output=True) + + mock_run.assert_called_once() + + assert result.returncode == 0 + + def test_github_workflow_local_execution(self, github_workflow_file): + """Test running a GitHub workflow locally with act""" + with patch('subprocess.run') as mock_run, \ + patch.dict(os.environ, {"GITHUB_TOKEN": "test_github_token"}): + + mock_run.return_value = MagicMock( + returncode=0, + stdout=b'[Test GitHub Workflow/test] Start image=catthehacker/ubuntu:act-latest\n' + b'[Test GitHub Workflow/test] docker run image=catthehacker/ubuntu:act-latest\n' + b'[Test GitHub Workflow/test] Success - Test GitHub Token\n' + b'[Test GitHub Workflow/test] Run Test GitHub Token\n' + b'[Test GitHub Workflow/test] Testing GitHub token\n' + b'[Test GitHub Workflow/test] GitHub token is valid\n' + ) + + workflow_dir = os.path.dirname(os.path.dirname(os.path.dirname(github_workflow_file))) + result = subprocess.run( + ['act', '-W', '.github/workflows/test_github_workflow.yml', '--secret', 'GITHUB_TOKEN=test_github_token'], + cwd=workflow_dir, + capture_output=True + ) + + mock_run.assert_called_once() + + assert result.returncode == 0 + + assert b'GitHub token is valid' in result.stdout + + def test_gitlab_workflow_local_execution(self, gitlab_workflow_file): + """Test running a GitLab workflow locally with act""" + with patch('subprocess.run') as mock_run, \ + patch.dict(os.environ, {"GITLAB_TOKEN": "test_gitlab_token"}): + + mock_run.return_value = MagicMock( + returncode=0, + stdout=b'[Test GitLab Workflow/test] Start image=catthehacker/ubuntu:act-latest\n' + b'[Test GitLab Workflow/test] docker run image=catthehacker/ubuntu:act-latest\n' + b'[Test GitLab Workflow/test] Success - Test GitLab Token\n' + b'[Test GitLab Workflow/test] Run Test GitLab Token\n' + b'[Test GitLab Workflow/test] Testing GitLab token\n' + b'[Test GitLab Workflow/test] GitLab token is valid\n' + ) + + workflow_dir = os.path.dirname(os.path.dirname(os.path.dirname(gitlab_workflow_file))) + result = subprocess.run( + ['act', '-W', '.github/workflows/test_gitlab_workflow.yml', '--secret', 'GITLAB_TOKEN=test_gitlab_token'], + cwd=workflow_dir, + capture_output=True + ) + + mock_run.assert_called_once() + + assert result.returncode == 0 + + assert b'GitLab token is valid' in result.stdout + + def test_pat_workflow_local_execution(self, pat_workflow_file): + """Test running a PAT workflow locally with act""" + with patch('subprocess.run') as mock_run, \ + patch.dict(os.environ, {"PAT_TOKEN": "test_pat_token"}): + + mock_run.return_value = MagicMock( + returncode=0, + stdout=b'[Test PAT Workflow/test] Start image=catthehacker/ubuntu:act-latest\n' + b'[Test PAT Workflow/test] docker run image=catthehacker/ubuntu:act-latest\n' + b'[Test PAT Workflow/test] Success - Test PAT Token\n' + b'[Test PAT Workflow/test] Run Test PAT Token\n' + b'[Test PAT Workflow/test] Testing PAT token\n' + b'[Test PAT Workflow/test] PAT token is valid\n' + ) + + workflow_dir = os.path.dirname(os.path.dirname(os.path.dirname(pat_workflow_file))) + result = subprocess.run( + ['act', '-W', '.github/workflows/test_pat_workflow.yml', '--secret', 'PAT_TOKEN=test_pat_token'], + cwd=workflow_dir, + capture_output=True + ) + + mock_run.assert_called_once() + + assert result.returncode == 0 + + assert b'PAT token is valid' in result.stdout + + def test_workflow_token_missing(self, github_workflow_file): + """Test workflow execution with missing token""" + with patch('subprocess.run') as mock_run: + + mock_run.return_value = MagicMock( + returncode=1, + stdout=b'[Test GitHub Workflow/test] Start image=catthehacker/ubuntu:act-latest\n' + b'[Test GitHub Workflow/test] docker run image=catthehacker/ubuntu:act-latest\n' + b'[Test GitHub Workflow/test] Run Test GitHub Token\n' + b'[Test GitHub Workflow/test] Testing GitHub token\n' + b'[Test GitHub Workflow/test] GitHub token is missing\n' + b'[Test GitHub Workflow/test] Failure - Test GitHub Token\n' + ) + + workflow_dir = os.path.dirname(os.path.dirname(os.path.dirname(github_workflow_file))) + result = subprocess.run( + ['act', '-W', '.github/workflows/test_github_workflow.yml'], + cwd=workflow_dir, + capture_output=True + ) + + mock_run.assert_called_once() + + assert result.returncode == 1 + + assert b'GitHub token is missing' in result.stdout