// swiftlint:disable file_length import ForgejoKit import Foundation // swiftlint:disable:next type_body_length struct Seeder { let baseURL: String let serviceName: String let composeFile: String private let adminUser = "testadmin" private let adminPass = "admin1234" private let testbotUser = "testbot" private let testbotPass = "testbot1234" private let readonlyUser = "readonlyuser" private let readonlyPass = "readonly1234" func seed() async throws { let adminClient = ForgejoClient( serverURL: baseURL, username: adminUser, password: adminPass, allowSelfSignedCertificates: true, ) let testbotClient = ForgejoClient( serverURL: baseURL, username: testbotUser, password: testbotPass, allowSelfSignedCertificates: true, ) let start = Date() let total = 7 func step(_ number: Int, _ label: String) { let elapsed = Int(Date().timeIntervalSince(start)) print("\n[\(number)/\(total)] \(label) (\(elapsed)s elapsed)") } step(1, "Creating admin user") try await withRetry(maxAttempts: 3, delay: .seconds(2), operation: "create admin user") { try createAdminUser() } step(2, "Creating API users") try await createAPIUsers(adminClient: adminClient) step(3, "Creating repositories") try await createRepositories(adminClient: adminClient) step(4, "Creating issues, files, and metadata") try await createIssuesFilesMetadata(adminClient: adminClient, testbotClient: testbotClient) step(5, "Creating pull requests") try await createPullRequests(testbotClient: testbotClient) step(6, "Setting pull request metadata") try await setPRMetadata(adminClient: adminClient, testbotClient: testbotClient) step(7, "Creating API token") try await createAPIToken(adminClient: adminClient) print("\nSeed data created successfully (\(Int(Date().timeIntervalSince(start)))s total).") } // MARK: - Phase 1: Create users private func createAdminUser() throws { print("Creating admin user...") try DockerExec.run( composeFile: composeFile, service: serviceName, command: [ "forgejo", "admin", "user", "create", "--admin", "--username", adminUser, "--password", adminPass, "--email", "testadmin@test.local", "--must-change-password=false", ], ) } private func createAPIUsers(adminClient: ForgejoClient) async throws { let adminService = AdminService(client: adminClient) print("Creating testbot user...") _ = try await adminService.createUser( username: testbotUser, password: testbotPass, email: "testbot@test.local", ) print("Creating readonlyuser...") _ = try await adminService.createUser( username: readonlyUser, password: readonlyPass, email: "readonlyuser@test.local", ) } // MARK: - Phase 2: Create repositories private func createRepositories(adminClient: ForgejoClient) async throws { let repoService = RepositoryService(client: adminClient) print("Creating repositories...") for (name, desc) in [ ("test-repo", nil as String?), ("test-repo-2", "A second test repository" as String?), ("archived-repo", "An archived repository" as String?), ("html-readme-repo", "Repo with HTML in README" as String?), ] { _ = try await repoService.createRepository( name: name, description: desc, autoInit: true, ) } print("Repositories created.") print("Archiving archived-repo...") try await withRetry(maxAttempts: 5, delay: .seconds(2), operation: "archive repo") { _ = try await repoService.editRepository( owner: adminUser, repo: "archived-repo", archived: true, ) } // Forgejo 15 enables actions on every new repo by default. Disable it on test-repo-2 so // `ActionsUITests.testActionsTabAbsentForRepoWithoutActions` exercises the negative path. print("Disabling actions on test-repo-2...") try await withRetry(maxAttempts: 5, delay: .seconds(2), operation: "disable actions") { _ = try await repoService.editRepository( owner: adminUser, repo: "test-repo-2", hasActions: false, ) } print("Updating html-readme-repo README...") let readmeSHA: String = try await withRetry(maxAttempts: 5, delay: .seconds(2), operation: "fetch README") { let file = try await repoService.fetchFileContent( owner: adminUser, repo: "html-readme-repo", path: "README.md", ) return file.sha } _ = try await repoService.updateFile( owner: adminUser, repo: "html-readme-repo", path: "README.md", content: htmlReadmeContent, sha: readmeSHA, message: "Add HTML to README", ) } // MARK: - Phase 3: Issues, files, metadata // swiftlint:disable:next function_body_length private func createIssuesFilesMetadata( adminClient: ForgejoClient, testbotClient: ForgejoClient, ) async throws { let adminIssueService = IssueService(client: adminClient) let testbotIssueService = IssueService(client: testbotClient) let repoService = RepositoryService(client: adminClient) print("Creating pagination test issues...") async let paginationTask: Void = { for index in 1 ... 25 { _ = try await adminIssueService.createIssue( owner: adminUser, repo: "test-repo-2", title: "Pagination test issue \(index)", body: nil, ) } }() print("Creating issues as testbot...") for index in 1 ... 3 { _ = try await testbotIssueService.createIssue( owner: adminUser, repo: "test-repo", title: "Test issue \(index) from integration tests", body: nil, ) } print("Setting up test-repo content...") try await withThrowingTaskGroup(of: Void.self) { group in group.addTask { try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "comment on issue #1") { _ = try await testbotIssueService.createComment( owner: adminUser, repo: "test-repo", index: 1, body: "This is a test comment from testbot", ) } } group.addTask { try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "edit issue #3") { _ = try await adminIssueService.editIssue( owner: adminUser, repo: "test-repo", index: 3, title: nil, body: "This is the **markdown** body for issue 3.\n\nIt has multiple paragraphs.", state: "closed", ) } } group.addTask { try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "edit issue #1") { _ = try await adminIssueService.editIssue( owner: adminUser, repo: "test-repo", index: 1, title: nil, body: "This is the **markdown** body for issue 1.\n\nIt has multiple paragraphs.", state: nil, ) } } group.addTask { try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "edit issue #2") { _ = try await adminIssueService.editIssue( owner: adminUser, repo: "test-repo", index: 2, title: nil, body: "This is the **markdown** body for issue 2.\n\nIt has multiple paragraphs.", state: nil, ) } } group.addTask { try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "add testbot collaborator") { try await repoService.addCollaborator( owner: adminUser, repo: "test-repo", username: testbotUser, permission: "write", ) } } group.addTask { try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "add readonly collaborator") { try await repoService.addCollaborator( owner: adminUser, repo: "test-repo", username: readonlyUser, permission: "read", ) } } group.addTask { try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "create label") { _ = try await repoService.createLabel( owner: adminUser, repo: "test-repo", name: "bug", color: "#ee0701", ) } } group.addTask { try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "create milestone") { _ = try await repoService.createMilestone( owner: adminUser, repo: "test-repo", title: "v1.0", description: "First release milestone", dueOn: "2026-03-01T00:00:00Z", ) } } try await group.waitForAll() } _ = try await repoService.createFile( owner: adminUser, repo: "test-repo", path: "hello.py", content: "print(\"Hello from Forgejo!\")", message: "Add hello.py", ) _ = try await repoService.createFile( owner: adminUser, repo: "test-repo", path: "src/main.py", content: "def main():\n print(\"Main module\")", message: "Add src/main.py", ) try await paginationTask print("Phase 3 done.") print("Setting issue metadata...") try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "add label to issue #1") { _ = try await adminIssueService.replaceLabels( owner: adminUser, repo: "test-repo", index: 1, labelIDs: [1], ) } try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "set milestone on issue #1") { _ = try await adminIssueService.editIssue( owner: adminUser, repo: "test-repo", index: 1, title: nil, body: nil, state: nil, milestone: 1, assignees: [adminUser], ) } } // MARK: - Phase 4: PRs private func createPullRequests(testbotClient: ForgejoClient) async throws { let repoService = RepositoryService(client: testbotClient) let prService = PullRequestService(client: testbotClient) print("Creating feature branch...") _ = try await repoService.createFile( owner: adminUser, repo: "test-repo", path: "feature.txt", content: "This file was added in a feature branch", message: "Add feature.txt", newBranch: "feature-branch", ) print("Creating pull request #4...") _ = try await prService.createPullRequest( owner: adminUser, repo: "test-repo", title: "Add feature file", head: "feature-branch", base: "main", body: "This PR adds a new feature file.", ) print("Creating merge branch...") _ = try await repoService.createFile( owner: adminUser, repo: "test-repo", path: "merge-test.txt", content: "This file is for merge testing", message: "Add merge-test.txt", newBranch: "merge-branch", ) print("Creating pull request #5...") _ = try await prService.createPullRequest( owner: adminUser, repo: "test-repo", title: "Merge test PR", head: "merge-branch", base: "main", body: "This PR is for testing the merge flow.", ) print("Creating squash-merge branch...") _ = try await repoService.createFile( owner: adminUser, repo: "test-repo", path: "squash-test.txt", content: "This file is for squash merge testing", message: "Add squash-test.txt", newBranch: "squash-branch", ) print("Creating pull request #6...") _ = try await prService.createPullRequest( owner: adminUser, repo: "test-repo", title: "Squash merge test PR", head: "squash-branch", base: "main", body: "This PR is for testing the squash merge flow.", ) print("Creating rebase branch...") _ = try await repoService.createFile( owner: adminUser, repo: "test-repo", path: "rebase-test.txt", content: "This file is for rebase testing", message: "Add rebase-test.txt", newBranch: "rebase-branch", ) print("Creating pull request #7...") _ = try await prService.createPullRequest( owner: adminUser, repo: "test-repo", title: "Rebase merge test PR", head: "rebase-branch", base: "main", body: "This PR is for testing the rebase merge flow.", ) } // MARK: - Phase 5: PR metadata private func setPRMetadata( adminClient: ForgejoClient, testbotClient: ForgejoClient, ) async throws { let testbotIssueService = IssueService(client: testbotClient) let adminPRService = PullRequestService(client: adminClient) let adminIssueService = IssueService(client: adminClient) print("Setting PR metadata...") try await withThrowingTaskGroup(of: Void.self) { group in group.addTask { try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "comment on PR #4") { _ = try await testbotIssueService.createComment( owner: adminUser, repo: "test-repo", index: 4, body: "Please review this PR when you get a chance.", ) } } group.addTask { try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "review PR #4") { _ = try await adminPRService.createReview( owner: adminUser, repo: "test-repo", index: 4, body: "Looks good so far, just a few comments.", event: "COMMENT", comments: [ CreateReviewComment( body: "Consider a more descriptive filename.", path: "feature.txt", oldPosition: nil, newPosition: 1, ), ], ) } } group.addTask { try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "set PR #4 metadata") { _ = try await adminIssueService.editIssue( owner: adminUser, repo: "test-repo", index: 4, title: nil, body: nil, state: nil, milestone: 1, assignees: [adminUser], ) } } try await group.waitForAll() } } // MARK: - Phase 6: API token private func createAPIToken(adminClient: ForgejoClient) async throws { let userService = UserService(client: adminClient) print("Creating API token for testadmin...") let token = try await userService.createToken( username: adminUser, name: "integration-test-token", scopes: [ "read:user", "write:user", "read:repository", "write:repository", "read:issue", "write:issue", "read:notification", "write:notification", "read:organization", ], ) let tokenPath = "/tmp/forgejo_test_token.txt" try token.sha1.write(toFile: tokenPath, atomically: true, encoding: .utf8) print("API token written to \(tokenPath)") } } // MARK: - HTML README content private let htmlReadmeContent = """ # HTML Readme Test Regular markdown paragraph.
Click to expand This content is hidden by default. - Item A - Item B
## Table
NameValue
Alpha1
Beta2
## Keyboard Shortcuts Press Ctrl+C to copy. ## Image placeholder ## Security Test
Click me
"""