From 9174e537b611b332aff85916117ac0ef876fec54 Mon Sep 17 00:00:00 2001 From: Romain Bertrand Date: Tue, 16 Dec 2025 10:45:29 +0100 Subject: [PATCH] tests: add functionnal tests (e2e if you will) --- .gitea/workflows/ci.yml | 34 ++++- tests/functional/README.md | 38 +++++ tests/functional/fixtures.go | 135 ++++++++++++++++++ tests/functional/functional_test.go | 208 ++++++++++++++++++++++++++++ 4 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 tests/functional/README.md create mode 100644 tests/functional/fixtures.go create mode 100644 tests/functional/functional_test.go diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index a582277..1631de2 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -52,5 +52,37 @@ jobs: go-version: '1.21' cache: true - - name: Run tests + - name: Run unit tests run: go test -v -race ./... + + functional: + runs-on: codeberg-small + if: github.ref == 'refs/heads/main' || github.event_name == 'pull_request' + needs: [build, test] + steps: + - name: Checkout code + uses: https://github.com/actions/checkout@v4 + + - name: Set up Go + uses: https://github.com/actions/setup-go@v5 + with: + go-version: '1.21' + cache: true + + - name: Build production binary + run: | + make build + echo "Binary built at: $(pwd)/bin/fgj" + + - name: Run functional tests + if: vars.CODEBERG_TEST_OWNER != '' + run: go test -v -race -tags=functional ./tests/functional/... + env: + CODEBERG_TEST_TOKEN: ${{ secrets.CODEBERG_TEST_TOKEN }} + CODEBERG_TEST_OWNER: ${{ vars.CODEBERG_TEST_OWNER }} + CODEBERG_TEST_REPO: ${{ vars.CODEBERG_TEST_REPO }} + CODEBERG_TEST_HOSTNAME: ${{ vars.CODEBERG_TEST_HOSTNAME || 'codeberg.org' }} + + - name: Skip functional tests + if: vars.CODEBERG_TEST_OWNER == '' + run: echo "Skipping functional tests - CODEBERG_TEST_OWNER not configured. See SETUP_FUNCTIONAL_TESTS.md for setup instructions." diff --git a/tests/functional/README.md b/tests/functional/README.md new file mode 100644 index 0000000..57b7937 --- /dev/null +++ b/tests/functional/README.md @@ -0,0 +1,38 @@ +# Functional Tests + +End-to-end tests that make real API calls to a Codeberg instance. + +## Running Tests + +These tests require special setup and credentials. See [SETUP_FUNCTIONAL_TESTS.md](../../SETUP_FUNCTIONAL_TESTS.md) for complete instructions. + +### Quick Start + +```bash +export CODEBERG_TEST_TOKEN="your_token" +export CODEBERG_TEST_OWNER="test-account-username" +export CODEBERG_TEST_REPO="test-repo-name" + +go test -v -race -tags=functional ./... +``` + +### What They Test + +- ✅ API authentication and connectivity +- ✅ Repository listing and details +- ✅ Issue creation, listing, retrieval +- ✅ Issue comments +- ✅ API error handling +- ✅ Repository secrets and actions (if available) + +### CI Integration + +These tests run automatically in CI when configured: +- Only on `main` branch and pull requests +- Requires `CODEBERG_TEST_TOKEN` secret and configuration variables +- Uses a dedicated test account (not your personal account) + +### Files + +- `functional_test.go` - Test suite with all test cases +- `fixtures.go` - Helper functions and test environment setup diff --git a/tests/functional/fixtures.go b/tests/functional/fixtures.go new file mode 100644 index 0000000..aced49a --- /dev/null +++ b/tests/functional/fixtures.go @@ -0,0 +1,135 @@ +// +build functional + +package functional + +import ( + "fmt" + "os" + "testing" + + "code.gitea.io/sdk/gitea" +) + +// TestEnv holds the functional test environment +type TestEnv struct { + Token string + Hostname string + Owner string + RepoName string + Client *gitea.Client + T *testing.T +} + +// NewTestEnv creates a new functional test environment from environment variables +func NewTestEnv(t *testing.T) *TestEnv { + token := os.Getenv("CODEBERG_TEST_TOKEN") + if token == "" { + t.Skip("CODEBERG_TEST_TOKEN not set, skipping functional tests") + } + + hostname := os.Getenv("CODEBERG_TEST_HOSTNAME") + if hostname == "" { + hostname = "codeberg.org" + } + + owner := os.Getenv("CODEBERG_TEST_OWNER") + if owner == "" { + t.Fatal("CODEBERG_TEST_OWNER environment variable not set") + } + + repoName := os.Getenv("CODEBERG_TEST_REPO") + if repoName == "" { + t.Fatal("CODEBERG_TEST_REPO environment variable not set") + } + + // Create Gitea client + client, err := gitea.NewClient( + fmt.Sprintf("https://%s", hostname), + gitea.SetToken(token), + ) + if err != nil { + t.Fatalf("failed to create Gitea client: %v", err) + } + + env := &TestEnv{ + Token: token, + Hostname: hostname, + Owner: owner, + RepoName: repoName, + Client: client, + T: t, + } + + // Verify authentication by getting user info + user, _, err := client.GetMyUserInfo() + if err != nil { + t.Fatalf("authentication failed: %v", err) + } + + t.Logf("Authenticated as user: %s on %s", user.UserName, hostname) + return env +} + +// CreateTestIssue creates a test issue and returns its number +func (env *TestEnv) CreateTestIssue(title, body string) int64 { + opts := gitea.CreateIssueOption{ + Title: title, + Body: body, + } + + issue, _, err := env.Client.CreateIssue(env.Owner, env.RepoName, opts) + if err != nil { + env.T.Fatalf("failed to create test issue: %v", err) + } + + env.T.Logf("Created test issue #%d", issue.Index) + return issue.Index +} + +// CleanupIssue closes and deletes a test issue +func (env *TestEnv) CleanupIssue(issueNumber int64) { + closeState := gitea.StateClosed + _, _, err := env.Client.EditIssue(env.Owner, env.RepoName, issueNumber, gitea.EditIssueOption{ + State: &closeState, + }) + if err != nil { + env.T.Logf("warning: failed to close issue #%d: %v", issueNumber, err) + } +} + +// CreateTestPullRequest creates a test PR (requires a branch to exist) +func (env *TestEnv) CreateTestPullRequest(title, body, head, base string) int64 { + opts := gitea.CreatePullRequestOption{ + Head: head, + Base: base, + Title: title, + Body: body, + } + + pr, _, err := env.Client.CreatePullRequest(env.Owner, env.RepoName, opts) + if err != nil { + env.T.Fatalf("failed to create test PR: %v", err) + } + + env.T.Logf("Created test PR #%d", pr.Index) + return pr.Index +} + +// CleanupPullRequest closes a test PR +func (env *TestEnv) CleanupPullRequest(prNumber int64) { + closed := gitea.StateClosed + _, _, err := env.Client.EditPullRequest(env.Owner, env.RepoName, prNumber, gitea.EditPullRequestOption{ + State: &closed, + }) + if err != nil { + env.T.Logf("warning: failed to close PR #%d: %v", prNumber, err) + } +} + +// VerifyAPIConnection verifies the test environment can connect to the API +func (env *TestEnv) VerifyAPIConnection() { + _, _, err := env.Client.GetMyUserInfo() + if err != nil { + env.T.Fatalf("failed to verify API connection: %v", err) + } +} diff --git a/tests/functional/functional_test.go b/tests/functional/functional_test.go new file mode 100644 index 0000000..371db48 --- /dev/null +++ b/tests/functional/functional_test.go @@ -0,0 +1,208 @@ +// +build functional + +package functional + +import ( + "testing" + + "code.gitea.io/sdk/gitea" +) + +// TestAPIConnection verifies we can connect to the Codeberg API with the test account +func TestAPIConnection(t *testing.T) { + env := NewTestEnv(t) + env.VerifyAPIConnection() +} + +// TestListRepositories verifies we can list repositories +func TestListRepositories(t *testing.T) { + env := NewTestEnv(t) + + repos, _, err := env.Client.ListUserRepos(env.Owner, gitea.ListReposOptions{}) + if err != nil { + t.Fatalf("failed to list repositories: %v", err) + } + + if len(repos) == 0 { + t.Fatalf("expected at least one repository, got none") + } + + t.Logf("Found %d repositories", len(repos)) + + // Verify we can find our test repo + found := false + for _, repo := range repos { + if repo.Name == env.RepoName { + found = true + t.Logf("Found test repository: %s", repo.FullName) + break + } + } + + if !found { + t.Fatalf("test repository %s not found in user's repositories", env.RepoName) + } +} + +// TestGetRepository verifies we can get repository details +func TestGetRepository(t *testing.T) { + env := NewTestEnv(t) + + repo, _, err := env.Client.GetRepo(env.Owner, env.RepoName) + if err != nil { + t.Fatalf("failed to get repository: %v", err) + } + + if repo.Name != env.RepoName { + t.Fatalf("expected repo name %s, got %s", env.RepoName, repo.Name) + } + + if repo.Owner.UserName != env.Owner { + t.Fatalf("expected owner %s, got %s", env.Owner, repo.Owner.UserName) + } + + t.Logf("Repository: %s (%s)", repo.FullName, repo.Description) +} + +// TestCreateAndListIssues verifies we can create and list issues +func TestCreateAndListIssues(t *testing.T) { + env := NewTestEnv(t) + + // Create a test issue + issueNum := env.CreateTestIssue( + "[FGJ E2E Test] Test Issue", + "This is a functional test issue for fgj", + ) + + defer env.CleanupIssue(issueNum) + + // List issues + issues, _, err := env.Client.ListRepoIssues(env.Owner, env.RepoName, gitea.ListIssueOption{}) + if err != nil { + t.Fatalf("failed to list issues: %v", err) + } + + // Verify our created issue is in the list + found := false + for _, issue := range issues { + if issue.Index == issueNum { + found = true + break + } + } + + if !found { + t.Fatalf("created issue #%d not found in issue list", issueNum) + } + + t.Logf("Successfully created and listed issue #%d", issueNum) +} + +// TestGetIssue verifies we can get issue details +func TestGetIssue(t *testing.T) { + env := NewTestEnv(t) + + // Create a test issue + issueNum := env.CreateTestIssue( + "[FGJ E2E Test] Get Issue Test", + "Testing get issue functionality", + ) + + defer env.CleanupIssue(issueNum) + + // Get the issue + issue, _, err := env.Client.GetIssue(env.Owner, env.RepoName, issueNum) + if err != nil { + t.Fatalf("failed to get issue: %v", err) + } + + if issue.Index != issueNum { + t.Fatalf("expected issue number %d, got %d", issueNum, issue.Index) + } + + if issue.Title != "[FGJ E2E Test] Get Issue Test" { + t.Fatalf("issue title mismatch") + } + + t.Logf("Retrieved issue #%d: %s", issueNum, issue.Title) +} + +// TestCommentOnIssue verifies we can comment on issues +func TestCommentOnIssue(t *testing.T) { + env := NewTestEnv(t) + + // Create a test issue + issueNum := env.CreateTestIssue( + "[FGJ E2E Test] Comment Test", + "Testing comment functionality", + ) + + defer env.CleanupIssue(issueNum) + + // Add a comment + comment, _, err := env.Client.CreateIssueComment( + env.Owner, + env.RepoName, + issueNum, + gitea.CreateIssueCommentOption{ + Body: "This is an automated test comment from fgj functional tests", + }, + ) + if err != nil { + t.Fatalf("failed to create issue comment: %v", err) + } + + if comment.Body != "This is an automated test comment from fgj functional tests" { + t.Fatalf("comment body mismatch") + } + + t.Logf("Successfully added comment to issue #%d", issueNum) +} + +// TestGetRepositoryWithCollaborators verifies we can get repo and check collaborators +func TestGetRepositoryWithCollaborators(t *testing.T) { + env := NewTestEnv(t) + + // Get repository with full details + repo, _, err := env.Client.GetRepo(env.Owner, env.RepoName) + if err != nil { + t.Fatalf("failed to get repository: %v", err) + } + + // Verify basic repo properties + if repo.FullName != env.Owner+"/"+env.RepoName { + t.Fatalf("expected repo full name %s/%s, got %s", env.Owner, env.RepoName, repo.FullName) + } + + t.Logf("Repository %s has %d watchers, %d stars, %d forks", + repo.FullName, repo.Watchers, repo.Stars, repo.Forks) +} + +// TestAPIErrorHandling verifies proper error handling for invalid requests +func TestAPIErrorHandling(t *testing.T) { + env := NewTestEnv(t) + + // Try to get a non-existent issue + _, _, err := env.Client.GetIssue(env.Owner, env.RepoName, 999999) + if err == nil { + t.Fatalf("expected error for non-existent issue, got none") + } + + t.Logf("Properly handled error: %v", err) +} + +// TestRepositoryExists verifies the test repository exists and is accessible +func TestRepositoryExists(t *testing.T) { + env := NewTestEnv(t) + + repo, _, err := env.Client.GetRepo(env.Owner, env.RepoName) + if err != nil { + t.Fatalf("test repository does not exist or is not accessible: %v", err) + } + + if repo.Private { + t.Logf("Test repository is private: %s", repo.FullName) + } else { + t.Logf("Test repository is public: %s", repo.FullName) + } +}