# Copyright (c) 2017 IBM Corp.
#
# 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.

import time

from tests.base import ZuulGithubAppTestCase, ZuulTestCase, simple_layout


class TestGithubRequirements(ZuulTestCase):
    """Test pipeline and trigger requirements"""
    config_file = 'zuul-github-driver.conf'
    scheduler_count = 1

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_pipeline_require_status(self):
        "Test pipeline requirement: status"
        project = 'org/project1'
        A = self.fake_github.openFakePullRequest(project, 'master', 'A')
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent('test me')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        # No status from zuul so should not be enqueued
        self.assertEqual(len(self.history), 0)

        # An error status should not cause it to be enqueued
        self.fake_github.setCommitStatus(project, A.head_sha, 'error',
                                         context='tenant-one/check')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A success status goes in
        self.fake_github.setCommitStatus(project, A.head_sha, 'success',
                                         context='tenant-one/check')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, 'project1-pipeline')

        # Trigger regex matched status
        self.fake_github.emitEvent(A.getCommentAddedEvent('test regex'))
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 2)
        self.assertEqual(self.history[1].name, 'project1-pipeline')

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_trigger_require_status(self):
        "Test trigger requirement: status"
        project = 'org/project1'
        A = self.fake_github.openFakePullRequest(project, 'master', 'A')
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent('trigger me')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        # No status from zuul so should not be enqueued
        self.assertEqual(len(self.history), 0)

        # An error status should not cause it to be enqueued
        self.fake_github.setCommitStatus(project, A.head_sha, 'error',
                                         context='tenant-one/check')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A success status goes in
        self.fake_github.setCommitStatus(project, A.head_sha, 'success',
                                         context='tenant-one/check')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, 'project1-pipeline')

        self.fake_github.emitEvent(A.getCommentAddedEvent('trigger regex'))
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 2)
        self.assertEqual(self.history[1].name, 'project1-pipeline')

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_trigger_on_status(self):
        "Test trigger on: status"
        project = 'org/project2'
        A = self.fake_github.openFakePullRequest(project, 'master', 'A')

        # Create second PR which contains the head of A in its history. Zuul
        # should not get disturbed by the existence of this one.
        self.fake_github.openFakePullRequest(
            project, 'master', 'A', base_sha=A.head_sha)

        # An error status should not cause it to be enqueued
        self.fake_github.setCommitStatus(project, A.head_sha, 'error',
                                         context='tenant-one/check')
        self.fake_github.emitEvent(A.getCommitStatusEvent('tenant-one/check',
                                                          state='error'))
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A success status from unknown user should not cause it to be
        # enqueued
        self.fake_github.setCommitStatus(project, A.head_sha, 'success',
                                         context='tenant-one/check',
                                         user='foo')
        self.fake_github.emitEvent(A.getCommitStatusEvent('tenant-one/check',
                                                          state='success',
                                                          user='foo'))
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A success status from zuul goes in
        self.fake_github.setCommitStatus(project, A.head_sha, 'success',
                                         context='tenant-one/check')
        self.fake_github.emitEvent(A.getCommitStatusEvent('tenant-one/check'))
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, 'project2-trigger')

        # An error status for a different context should not cause it to be
        # enqueued
        self.fake_github.setCommitStatus(project, A.head_sha, 'error',
                                         context='tenant-one/gate')
        self.fake_github.emitEvent(A.getCommitStatusEvent('tenant-one/gate',
                                                          state='error'))
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)

        # A success status with a regex match goes in
        self.fake_github.emitEvent(A.getCommitStatusEvent('cooltest',
                                                          user='other-ci'))
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 2)
        self.assertEqual(self.history[1].name, 'project2-trigger')

    @simple_layout("layouts/requirements-github.yaml", driver="github")
    def test_trigger_on_check_run(self):
        """Test trigger on: check_run"""
        project = "org/project15"
        A = self.fake_github.openFakePullRequest(project, "master", "A")

        # A check_run request with a different name should not cause it to be
        # enqueued.
        self.fake_github.emitEvent(
            A.getCheckRunRequestedEvent("tenant-one/different-check")
        )
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A check_run request with the correct name, but for a different app
        # should not cause it to be enqueued.
        self.fake_github.emitEvent(
            A.getCheckRunRequestedEvent("tenant-one/check", app="other-ci")
        )
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A check_run request with the correct name for the correct app should
        # cause it to be enqueued.
        self.fake_github.emitEvent(
            A.getCheckRunRequestedEvent("tenant-one/check"))

        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, "project15-check-run")

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_pipeline_require_review_username(self):
        "Test pipeline requirement: review username"

        A = self.fake_github.openFakePullRequest('org/project3', 'master', 'A')
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent('test me')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        # No approval from derp so should not be enqueued
        self.assertEqual(len(self.history), 0)

        # Add an approved review from derp
        A.addReview('derp', 'APPROVED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, 'project3-reviewusername')

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_pipeline_require_review_state(self):
        "Test pipeline requirement: review state"

        A = self.fake_github.openFakePullRequest('org/project4', 'master', 'A')
        # Add derp to writers
        A.writers.extend(('derp', 'werp'))
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent('test me')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        # No positive review from derp so should not be enqueued
        self.assertEqual(len(self.history), 0)

        # A negative review from derp should not cause it to be enqueued
        A.addReview('derp', 'CHANGES_REQUESTED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A negative review from werp should not cause it to be enqueued
        A.addReview('werp', 'CHANGES_REQUESTED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A positive from nobody should not cause it to be enqueued
        A.addReview('nobody', 'APPROVED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A positive review from derp should still be blocked by the
        # negative review from werp
        A.addReview('derp', 'APPROVED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A positive review from werp should cause it to be enqueued
        A.addReview('werp', 'APPROVED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, 'project4-reviewreq')

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_pipeline_require_review_user_state(self):
        "Test pipeline requirement: review state from user"

        A = self.fake_github.openFakePullRequest('org/project5', 'master', 'A')
        # Add derp and herp to writers
        A.writers.extend(('derp', 'herp'))
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent('test me')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        # No positive review from derp so should not be enqueued
        self.assertEqual(len(self.history), 0)

        # A negative review from derp should not cause it to be enqueued
        A.addReview('derp', 'CHANGES_REQUESTED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A positive from nobody should not cause it to be enqueued
        A.addReview('nobody', 'APPROVED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A positive review from herp (a writer) should not cause it to be
        # enqueued
        A.addReview('herp', 'APPROVED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A positive review from derp should cause it to be enqueued
        A.addReview('derp', 'APPROVED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, 'project5-reviewuserstate')

# TODO: Implement reject on approval username/state

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_pipeline_require_review_latest_user_state(self):
        "Test pipeline requirement: review state from user"

        A = self.fake_github.openFakePullRequest('org/project5', 'master', 'A')
        # Add derp and herp to writers
        A.writers.extend(('derp', 'herp'))
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent('test me')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        # No positive review from derp so should not be enqueued
        self.assertEqual(len(self.history), 0)

        # The first negative review from derp should not cause it to be
        # enqueued
        A.addReview('derp', 'CHANGES_REQUESTED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A positive review from derp should cause it to be enqueued
        A.addReview('derp', 'APPROVED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, 'project5-reviewuserstate')

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_pipeline_require_review_write_perms(self):
        "Test pipeline requirement: review from user with write"

        A = self.fake_github.openFakePullRequest('org/project4', 'master', 'A')
        # Add herp to admins
        A.admins.append('herp')
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent('test me')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        # No positive review from derp so should not be enqueued
        self.assertEqual(len(self.history), 0)

        # The first review is from a reader, and thus should not be enqueued
        A.addReview('derp', 'APPROVED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A positive review from herp should cause it to be enqueued
        A.addReview('herp', 'APPROVED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, 'project4-reviewreq')

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_pipeline_require_review_comment_masked(self):
        "Test pipeline requirement: review comments on top of votes"

        A = self.fake_github.openFakePullRequest('org/project5', 'master', 'A')
        # Add derp to writers
        A.writers.append('derp')
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent('test me')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        # No positive review from derp so should not be enqueued
        self.assertEqual(len(self.history), 0)

        # The first negative review from derp should not cause it to be
        # enqueued
        A.addReview('derp', 'CHANGES_REQUESTED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A positive review is required, so provide it
        A.addReview('derp', 'APPROVED')

        # Add a comment review on top to make sure we can still enqueue
        A.addReview('derp', 'COMMENTED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, 'project5-reviewuserstate')

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_require_review_newer_than(self):

        A = self.fake_github.openFakePullRequest('org/project6', 'master', 'A')
        # Add derp and herp to writers
        A.writers.extend(('derp', 'herp'))
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent('test me')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        # No positive review from derp so should not be enqueued
        self.assertEqual(len(self.history), 0)

        # Add a too-old positive review, should not be enqueued
        submitted_at = time.time() - 72 * 60 * 60
        A.addReview('derp', 'APPROVED',
                    submitted_at)
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # Add a recent positive review
        submitted_at = time.time() - 12 * 60 * 60
        A.addReview('derp', 'APPROVED', submitted_at)
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, 'project6-newerthan')

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_require_review_older_than(self):

        A = self.fake_github.openFakePullRequest('org/project7', 'master', 'A')
        # Add derp and herp to writers
        A.writers.extend(('derp', 'herp'))
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent('test me')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        # No positive review from derp so should not be enqueued
        self.assertEqual(len(self.history), 0)

        # Add a too-new positive, should not be enqueued
        submitted_at = time.time() - 12 * 60 * 60
        A.addReview('derp', 'APPROVED',
                    submitted_at)
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # Add an old enough positive, should enqueue
        submitted_at = time.time() - 72 * 60 * 60
        A.addReview('herp', 'APPROVED', submitted_at)
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, 'project7-olderthan')

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_require_open(self):

        A = self.fake_github.openFakePullRequest('org/project8', 'master', 'A')
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent('test me')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()

        # PR is open, we should have enqueued
        self.assertEqual(len(self.history), 1)

        # close the PR and try again
        A.state = 'closed'
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        # PR is closed, should not trigger
        self.assertEqual(len(self.history), 1)

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_reject_open(self):

        A = self.fake_github.openFakePullRequest('org/project13', 'master',
                                                 'A')
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent('test me')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()

        # PR is open, we should not have enqueued
        self.assertEqual(len(self.history), 0)

        # close the PR and try again
        A.state = 'closed'
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        # PR is closed, should trigger
        self.assertEqual(len(self.history), 1)

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_require_current(self):

        A = self.fake_github.openFakePullRequest('org/project9', 'master',
                                                 'A')
        # A sync event that we will keep submitting to trigger
        sync = A.getPullRequestSynchronizeEvent()
        self.fake_github.emitEvent(sync)
        self.waitUntilSettled()

        # PR head is current should enqueue
        self.assertEqual(len(self.history), 1)

        # Add a commit to the PR, re-issue the original comment event
        A.addCommit()
        self.fake_github.emitEvent(sync)
        self.waitUntilSettled()
        # Event hash is not current, should not trigger
        self.assertEqual(len(self.history), 1)

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_reject_current(self):

        A = self.fake_github.openFakePullRequest('org/project14', 'master',
                                                 'A')
        # A sync event that we will keep submitting to trigger
        sync = A.getPullRequestSynchronizeEvent()
        self.fake_github.emitEvent(sync)
        self.waitUntilSettled()

        # PR head is current, should not enqueue
        self.assertEqual(len(self.history), 0)

        # Add a commit to the PR, re-issue the original comment event
        A.addCommit()
        self.fake_github.emitEvent(sync)
        self.waitUntilSettled()
        # Event hash is not current, should trigger
        self.assertEqual(len(self.history), 1)

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_require_draft(self):

        A = self.fake_github.openFakePullRequest('org/project17', 'master',
                                                 'A', draft=True)
        # A sync event that we will keep submitting to trigger
        sync = A.getPullRequestSynchronizeEvent()
        self.fake_github.emitEvent(sync)
        self.waitUntilSettled()

        # PR is a draft, should enqueue
        self.assertEqual(len(self.history), 1)

        # Make the PR not a draft
        A.draft = False
        self.fake_github.emitEvent(sync)
        self.waitUntilSettled()
        # PR is not a draft, should not enqueue
        self.assertEqual(len(self.history), 1)

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_reject_draft(self):

        A = self.fake_github.openFakePullRequest('org/project18', 'master',
                                                 'A', draft=True)
        # A sync event that we will keep submitting to trigger
        sync = A.getPullRequestSynchronizeEvent()
        self.fake_github.emitEvent(sync)
        self.waitUntilSettled()

        # PR is a draft, should not enqueue
        self.assertEqual(len(self.history), 0)

        # Make the PR not a draft
        A.draft = False
        self.fake_github.emitEvent(sync)
        self.waitUntilSettled()
        # PR is not a draft, should enqueue
        self.assertEqual(len(self.history), 1)

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_pipeline_require_label(self):
        "Test pipeline requirement: label"
        A = self.fake_github.openFakePullRequest('org/project10', 'master',
                                                 'A')
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent('test me')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        # No label so should not be enqueued
        self.assertEqual(len(self.history), 0)

        # A derp label should not cause it to be enqueued
        A.addLabel('derp')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # An approved label goes in
        A.addLabel('approved')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, 'project10-label')

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_pipeline_reject_label(self):
        "Test pipeline reject: label"
        A = self.fake_github.openFakePullRequest('org/project11', 'master',
                                                 'A')
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent('test me')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        # No label so should not be enqueued
        self.assertEqual(len(self.history), 0)

        # A do-not-merge label should not cause it to be enqueued
        A.addLabel('do-not-merge')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # An approved label should still not enqueue due to d-n-m
        A.addLabel('approved')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # Remove do-not-merge should enqueue
        A.removeLabel('do-not-merge')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, 'project11-label')

    @simple_layout('layouts/requirements-github.yaml', driver='github')
    def test_pipeline_reject_status(self):
        "Test pipeline reject: status"
        project = 'org/project12'
        A = self.fake_github.openFakePullRequest(project, 'master', 'A')

        # Set rejected error status
        self.fake_github.setCommitStatus(project, A.head_sha, 'error',
                                         context='tenant-one/check')

        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent('test me')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        # Status should cause it to be rejected
        self.assertEqual(len(self.history), 0)

        # Test that also the regex matched pipeline doesn't trigger
        self.fake_github.emitEvent(A.getCommentAddedEvent('test regex'))
        self.waitUntilSettled()
        # Status should cause it to be rejected
        self.assertEqual(len(self.history), 0)

        self.fake_github.setCommitStatus(project, A.head_sha, 'success',
                                         context='tenant-one/check')
        # Now that status is not error, it should be enqueued
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, 'project12-status')

        # Test that also the regex matched pipeline triggers now
        self.fake_github.emitEvent(A.getCommentAddedEvent('test regex'))
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 2)
        self.assertEqual(self.history[1].name, 'project12-status')


class TestGithubAppRequirements(ZuulGithubAppTestCase):
    """Test pipeline and trigger requirements with app authentication"""
    config_file = 'zuul-github-driver.conf'
    scheduler_count = 1

    @simple_layout("layouts/requirements-github.yaml", driver="github")
    def test_pipeline_require_check_run(self):
        "Test pipeline requirement: status (reported via a check run)"
        project = "org/project16"
        github = self.fake_github.getGithubClient()
        repo = github.repo_from_project(project)

        A = self.fake_github.openFakePullRequest(project, "master", "A")
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent("trigger me")
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()

        # No status from zuul, so nothing should be enqueued
        self.assertEqual(len(self.history), 0)

        # An error check run should also not cause it to be enqueued
        repo.create_check_run(
            A.head_sha,
            "tenant-one/check",
            conclusion="failure",
            app="check-run",
        )
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A success check run goes in, ready to be enqueued
        repo.create_check_run(
            A.head_sha,
            "tenant-one/check",
            conclusion="success",
            app="check-run",
        )
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)


class TestGithubTriggerRequirements(ZuulTestCase):
    """Test pipeline and trigger requirements"""
    config_file = 'zuul-github-driver.conf'
    scheduler_count = 1

    @simple_layout('layouts/github-trigger-requirements.yaml', driver='github')
    def test_require_status(self):
        # Test trigger require-status
        jobname = 'require-status'
        project = 'org/project'
        A = self.fake_github.openFakePullRequest(project, 'master', 'A')
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent(f'test {jobname}')

        # No status from zuul so should not be enqueued
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # An error status should not cause it to be enqueued
        self.fake_github.setCommitStatus(project, A.head_sha, 'error',
                                         context='tenant-one/check')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A success status goes in
        self.fake_github.setCommitStatus(project, A.head_sha, 'success',
                                         context='tenant-one/check')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, jobname)

    @simple_layout('layouts/github-trigger-requirements.yaml', driver='github')
    def test_reject_status(self):
        # Test trigger reject-status
        jobname = 'reject-status'
        project = 'org/project'
        A = self.fake_github.openFakePullRequest(project, 'master', 'A')
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent(f'test {jobname}')

        # No status from zuul so should be enqueued
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, jobname)

        # A failure status should not cause it to be enqueued
        self.fake_github.setCommitStatus(project, A.head_sha, 'failure',
                                         context='tenant-one/check')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)

        # A success status goes in
        self.fake_github.setCommitStatus(project, A.head_sha, 'success',
                                         context='tenant-one/check')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 2)
        self.assertEqual(self.history[1].name, jobname)

    @simple_layout('layouts/github-trigger-requirements.yaml', driver='github')
    def test_require_review(self):
        # Test trigger require-review
        jobname = 'require-review'
        project = 'org/project'
        A = self.fake_github.openFakePullRequest(project, 'master', 'A')
        A.writers.extend(('maintainer',))
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent(f'test {jobname}')

        # No review so should not be enqueued
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # An changes requested review should not cause it to be enqueued
        A.addReview('maintainer', 'CHANGES_REQUESTED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A positive review goes in
        A.addReview('maintainer', 'APPROVED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, jobname)

    @simple_layout('layouts/github-trigger-requirements.yaml', driver='github')
    def test_reject_review(self):
        # Test trigger reject-review
        jobname = 'reject-review'
        project = 'org/project'
        A = self.fake_github.openFakePullRequest(project, 'master', 'A')
        A.writers.extend(('maintainer',))
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent(f'test {jobname}')

        # No review so should be enqueued
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, jobname)

        # An changes requested review should not cause it to be enqueued
        A.addReview('maintainer', 'CHANGES_REQUESTED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)

        # A positive review goes in
        A.addReview('maintainer', 'APPROVED')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 2)
        self.assertEqual(self.history[1].name, jobname)

    @simple_layout('layouts/github-trigger-requirements.yaml', driver='github')
    def test_require_label(self):
        # Test trigger require-label
        jobname = 'require-label'
        project = 'org/project'
        A = self.fake_github.openFakePullRequest(project, 'master', 'A')
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent(f'test {jobname}')

        # No label so should not be enqueued
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # A random should not cause it to be enqueued
        A.addLabel('foobar')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 0)

        # An approved label goes in
        A.addLabel('approved')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, jobname)

    @simple_layout('layouts/github-trigger-requirements.yaml', driver='github')
    def test_reject_label(self):
        # Test trigger reject-label
        jobname = 'reject-label'
        project = 'org/project'
        A = self.fake_github.openFakePullRequest(project, 'master', 'A')
        # A comment event that we will keep submitting to trigger
        comment = A.getCommentAddedEvent(f'test {jobname}')

        # No label so should be enqueued
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)
        self.assertEqual(self.history[0].name, jobname)

        # A rejected label should not cause it to be enqueued
        A.addLabel('rejected')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 1)

        # Any other label, it goes in
        A.removeLabel('rejected')
        A.addLabel('okay')
        self.fake_github.emitEvent(comment)
        self.waitUntilSettled()
        self.assertEqual(len(self.history), 2)
        self.assertEqual(self.history[1].name, jobname)
