# coding=utf-8

import datetime
import json as py_json
import logging
import re
import time
import unittest
from calendar import timegm
from contextlib import contextmanager
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from httmock import HTTMock, all_requests, urlmatch
from mock import patch
from playhouse.test_utils import _QueryLogHandler, assert_query_count

from app import (
    all_queues,
    app,
    config_provider,
    docker_v2_signing_key,
    dockerfile_build_queue,
    notification_queue,
    storage,
)
from buildtrigger.basehandler import BuildTriggerHandler
from data import database, model
from data.database import Repository as RepositoryTable
from data.database import RepositoryActionCount
from data.logs_model import logs_model
from data.registry_model import registry_model
from endpoints.api import api, api_bp
from endpoints.api.billing import (
    ListPlans,
    OrganizationCard,
    OrganizationPlan,
    OrganizationRhSku,
    OrganizationRhSkuBatchRemoval,
    OrganizationRhSkuSubscriptionField,
    UserCard,
    UserPlan,
    UserSkuList,
)
from endpoints.api.build import (
    RepositoryBuildList,
    RepositoryBuildResource,
    RepositoryBuildStatus,
)
from endpoints.api.discovery import DiscoveryResource
from endpoints.api.error import Error
from endpoints.api.globalmessages import GlobalUserMessage, GlobalUserMessages
from endpoints.api.logs import (
    OrgAggregateLogs,
    OrgLogs,
    RepositoryAggregateLogs,
    RepositoryLogs,
    UserAggregateLogs,
    UserLogs,
)
from endpoints.api.manifest import (
    ManageRepositoryManifestLabel,
    RepositoryManifestLabels,
)
from endpoints.api.organization import (
    ApplicationInformation,
    Organization,
    OrganizationApplicationResetClientSecret,
    OrganizationApplicationResource,
    OrganizationApplications,
    OrganizationList,
    OrganizationMember,
    OrganizationMemberList,
    OrgPrivateRepositories,
)
from endpoints.api.permission import (
    RepositoryTeamPermission,
    RepositoryTeamPermissionList,
    RepositoryUserPermission,
    RepositoryUserPermissionList,
)
from endpoints.api.prototype import PermissionPrototype, PermissionPrototypeList
from endpoints.api.repoemail import RepositoryAuthorizedEmail
from endpoints.api.repository import Repository, RepositoryList, RepositoryVisibility
from endpoints.api.repository_models_pre_oci import REPOS_PER_PAGE
from endpoints.api.repositorynotification import (
    RepositoryNotification,
    RepositoryNotificationList,
    TestRepositoryNotification,
)
from endpoints.api.repotoken import RepositoryToken, RepositoryTokenList
from endpoints.api.robot import (
    OrgRobot,
    OrgRobotList,
    RegenerateOrgRobot,
    RegenerateUserRobot,
    UserRobot,
    UserRobotList,
)
from endpoints.api.search import ConductSearch, EntitySearch
from endpoints.api.secscan import RepositoryManifestSecurity
from endpoints.api.superuser import (
    SuperUserLogs,
    SuperUserManagement,
    SuperUserServiceKey,
    SuperUserServiceKeyApproval,
    SuperUserServiceKeyManagement,
    SuperUserTakeOwnership,
)
from endpoints.api.tag import ListRepositoryTags, RepositoryTag, RestoreTag
from endpoints.api.team import (
    InviteTeamMember,
    OrganizationTeam,
    TeamMember,
    TeamMemberInvite,
    TeamMemberList,
    TeamPermissions,
)
from endpoints.api.trigger import (
    ActivateBuildTrigger,
    BuildTrigger,
    BuildTriggerActivate,
    BuildTriggerAnalyze,
    BuildTriggerFieldValues,
    BuildTriggerList,
    BuildTriggerSourceNamespaces,
    BuildTriggerSources,
    BuildTriggerSubdirs,
    TriggerBuildList,
)
from endpoints.api.user import (
    ConvertToOrganization,
    PrivateRepositories,
    Signin,
    Signout,
    StarredRepository,
    StarredRepositoryList,
    User,
    UserAuthorization,
    UserAuthorizationList,
    UserNotification,
    UserNotificationList,
)
from endpoints.building import PreparedBuild
from endpoints.webhooks import webhooks
from initdb import finished_database_for_testing, setup_database_for_testing
from test.helpers import assert_action_logged, check_transitive_modifications
from util.morecollections import AttrDict
from util.secscan.v4.fake import fake_security_scanner

try:
    app.register_blueprint(api_bp, url_prefix="/api")
except ValueError:
    # This blueprint was already registered
    pass

app.register_blueprint(webhooks, url_prefix="/webhooks")

# The number of queries we run for guests on API calls.
BASE_QUERY_COUNT = 0

# The number of queries we run for logged in users on API calls.
BASE_LOGGEDIN_QUERY_COUNT = BASE_QUERY_COUNT + 1

# The number of queries we run for logged in users on API calls that check
# access permissions.
BASE_PERM_ACCESS_QUERY_COUNT = BASE_LOGGEDIN_QUERY_COUNT + 2

NO_ACCESS_USER = "freshuser"
READ_ACCESS_USER = "reader"
ADMIN_ACCESS_USER = "devtable"
PUBLIC_USER = "public"

ADMIN_ACCESS_EMAIL = "jschorr@devtable.com"

ORG_REPO = "orgrepo"

ORGANIZATION = "buynlarge"

SUBSCRIPTION_USER = "subscription"
SUBSCRIPTION_ORG = "subscriptionsorg"

NEW_USER_DETAILS = {
    "username": "bobby",
    "password": "password",
    "email": "bobby@tables.com",
}

FAKE_APPLICATION_CLIENT_ID = "deadbeef"

CSRF_TOKEN_KEY = "_csrf_token"


class AppConfigChange(object):
    """
    AppConfigChange takes a dictionary that overrides the global app config for a given block of
    code.

    The values are restored on exit.
    """

    def __init__(self, changes=None):
        self._changes = changes or {}
        self._originals = {}
        self._to_rm = []

    def __enter__(self):
        for key in list(self._changes.keys()):
            try:
                self._originals[key] = app.config[key]
            except KeyError:
                self._to_rm.append(key)
            app.config[key] = self._changes[key]

    def __exit__(self, type, value, traceback):
        for key in list(self._originals.keys()):
            app.config[key] = self._originals[key]

        for key in self._to_rm:
            del app.config[key]


class ApiTestCase(unittest.TestCase):
    maxDiff = None

    def _add_csrf(self, without_csrf, explicit_csrf=None):
        parts = urlparse(without_csrf)
        query = parse_qs(parts[4])

        with self.app.session_transaction() as sess:
            if explicit_csrf is not None:
                query[CSRF_TOKEN_KEY] = explicit_csrf
            else:
                sess[CSRF_TOKEN_KEY] = "something"
                query[CSRF_TOKEN_KEY] = sess[CSRF_TOKEN_KEY]

        return urlunparse(list(parts[0:4]) + [urlencode(query)] + list(parts[5:]))

    def url_for(self, resource_name, params=None, skip_csrf=False, explicit_csrf=None):
        params = params or {}
        url = api.url_for(resource_name, **params)
        if not skip_csrf:
            url = self._add_csrf(url, explicit_csrf)
        return url

    def setUp(self):
        setup_database_for_testing(self)
        self.app = app.test_client()
        self.ctx = app.test_request_context()
        self.ctx.__enter__()

    def tearDown(self):
        finished_database_for_testing(self)
        config_provider.clear()
        self.ctx.__exit__(True, None, None)

    def setCsrfToken(self, token):
        with self.app.session_transaction() as sess:
            sess[CSRF_TOKEN_KEY] = token

    @contextmanager
    def toggleFeature(self, name, enabled):
        import features

        previous_value = getattr(features, name)
        setattr(features, name, enabled)
        yield
        setattr(features, name, previous_value)

    def getJsonResponse(self, resource_name, params={}, expected_code=200):
        rv = self.app.get(api.url_for(resource_name, **params))
        self.assertEqual(expected_code, rv.status_code)
        data = rv.data
        parsed = py_json.loads(data)
        return parsed

    def postResponse(
        self, resource_name, params={}, data={}, file=None, headers=None, expected_code=200
    ):
        data = py_json.dumps(data)

        headers = headers or {}
        headers.update({"Content-Type": "application/json"})

        if file is not None:
            data = {"file": file}
            headers = None

        rv = self.app.post(self.url_for(resource_name, params), data=data, headers=headers)
        self.assertEqual(rv.status_code, expected_code)
        return rv.data

    def getResponse(self, resource_name, params={}, expected_code=200):
        rv = self.app.get(api.url_for(resource_name, **params))
        self.assertEqual(rv.status_code, expected_code)
        return rv.data

    def putResponse(self, resource_name, params={}, data={}, expected_code=200):
        rv = self.app.put(
            self.url_for(resource_name, params),
            data=py_json.dumps(data),
            headers={"Content-Type": "application/json"},
        )
        self.assertEqual(rv.status_code, expected_code)
        return rv.data

    def deleteResponse(self, resource_name, params={}, expected_code=204):
        rv = self.app.delete(self.url_for(resource_name, params))

        if rv.status_code != expected_code:
            print("Mismatch data for resource DELETE %s: %s" % (resource_name, rv.data))

        self.assertEqual(rv.status_code, expected_code)
        return rv.data

    def deleteEmptyResponse(self, resource_name, params={}, expected_code=204):
        rv = self.app.delete(self.url_for(resource_name, params))
        self.assertEqual(rv.status_code, expected_code)
        self.assertEqual(rv.data, b"")  # ensure response body empty
        return

    def postJsonResponse(self, resource_name, params={}, data={}, expected_code=200):
        rv = self.app.post(
            self.url_for(resource_name, params),
            data=py_json.dumps(data),
            headers={"Content-Type": "application/json"},
        )

        if rv.status_code != expected_code:
            print("Mismatch data for resource POST %s: %s" % (resource_name, rv.data))

        self.assertEqual(rv.status_code, expected_code)
        data = rv.data
        parsed = py_json.loads(data.decode("utf-8"))
        return parsed

    def putJsonResponse(
        self,
        resource_name,
        params={},
        data={},
        expected_code=200,
        skip_csrf=False,
        explicit_csrf=None,
    ):
        rv = self.app.put(
            self.url_for(resource_name, params, skip_csrf, explicit_csrf),
            data=py_json.dumps(data),
            headers={"Content-Type": "application/json"},
        )

        if rv.status_code != expected_code:
            print("Mismatch data for resource PUT %s: %s" % (resource_name, rv.data))

        self.assertEqual(rv.status_code, expected_code)
        data = rv.data
        parsed = py_json.loads(data.decode("utf-8"))
        return parsed

    def assertNotInTeam(self, data, membername):
        for memberData in data["members"]:
            if memberData["name"] == membername:
                self.fail(membername + " found in team: " + data["name"])

    def assertInTeam(self, data, membername):
        for member_data in data["members"]:
            if member_data["name"] == membername:
                return

        self.fail(membername + " not found in team: " + data["name"])

    def login(self, username, password="password"):
        return self.postJsonResponse(Signin, data=dict(username=username, password=password))


class TestCSRFFailure(ApiTestCase):
    def test_csrf_failure(self):
        self.login(READ_ACCESS_USER)

        # Make sure a simple post call succeeds.
        self.putJsonResponse(User, data=dict(password="newpasswordiscool"))

        # Change the session's CSRF token.
        self.setCsrfToken("someinvalidtoken")

        # Verify that the call now fails.
        self.putJsonResponse(
            User, data=dict(password="newpasswordiscool"), expected_code=403, explicit_csrf="foobar"
        )

    def test_csrf_failure_empty_token(self):
        self.login(READ_ACCESS_USER)

        # Change the session's CSRF token to be empty.
        self.setCsrfToken("")

        # Verify that the call now fails.
        self.putJsonResponse(
            User, data=dict(password="newpasswordiscool"), expected_code=403, explicit_csrf="foobar"
        )

    def test_csrf_failure_missing_token(self):
        self.login(READ_ACCESS_USER)

        # Make sure a simple post call without a token at all fails.
        self.putJsonResponse(
            User, data=dict(password="newpasswordiscool"), skip_csrf=True, expected_code=403
        )

        # Change the session's CSRF token to be empty.
        self.setCsrfToken("")

        # Verify that the call still fails.
        self.putJsonResponse(
            User,
            data=dict(password="newpasswordiscool"),
            skip_csrf=True,
            expected_code=403,
            explicit_csrf="foobar",
        )


class TestDiscovery(ApiTestCase):
    def test_discovery(self):
        json = self.getJsonResponse(DiscoveryResource)
        assert "paths" in json


class TestErrorDescription(ApiTestCase):
    def test_get_error(self):
        json = self.getJsonResponse(Error, params=dict(error_type="not_found"))
        assert json["title"] == "not_found"
        assert "type" in json
        assert "description" in json


class TestPlans(ApiTestCase):
    def test_plans(self):
        json = self.getJsonResponse(ListPlans)
        found = set([])
        for method_info in json["plans"]:
            found.add(method_info["stripeId"])

        assert "free" in found


class TestLoggedInUser(ApiTestCase):
    def test_guest(self):
        self.getJsonResponse(User, expected_code=401)

    def test_user(self):
        self.login(READ_ACCESS_USER)
        json = self.getJsonResponse(User)
        assert json["anonymous"] == False
        assert json["username"] == READ_ACCESS_USER


class TestUserStarredRepositoryList(ApiTestCase):
    def test_get_stars_guest(self):
        self.getJsonResponse(StarredRepositoryList, expected_code=401)

    def test_get_stars_user(self):
        self.login(READ_ACCESS_USER)

        # Queries: Base + the list query
        with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT):
            self.getJsonResponse(StarredRepositoryList, expected_code=200)

    def test_star_repo_guest(self):
        self.postJsonResponse(
            StarredRepositoryList,
            data={
                "namespace": "public",
                "repository": "publicrepo",
            },
            expected_code=401,
        )

    def test_star_and_unstar_repo_user(self):
        self.login(READ_ACCESS_USER)

        # Queries: Base + the list query
        with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT):
            json = self.getJsonResponse(StarredRepositoryList)
            assert json["repositories"] == []

        json = self.postJsonResponse(
            StarredRepositoryList,
            data={
                "namespace": "public",
                "repository": "publicrepo",
            },
            expected_code=201,
        )
        assert json["namespace"] == "public"
        assert json["repository"] == "publicrepo"

        self.deleteEmptyResponse(
            StarredRepository, params=dict(repository="public/publicrepo"), expected_code=204
        )

        json = self.getJsonResponse(StarredRepositoryList)
        assert json["repositories"] == []


class TestUserNotification(ApiTestCase):
    def test_get(self):
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(UserNotificationList)

        # Make sure each notification can be retrieved.
        for notification in json["notifications"]:
            njson = self.getJsonResponse(UserNotification, params=dict(uuid=notification["id"]))
            self.assertEqual(notification["id"], njson["id"])

        # Update a notification.
        assert json["notifications"]
        assert not json["notifications"][0]["dismissed"]

        notification = json["notifications"][0]
        pjson = self.putJsonResponse(
            UserNotification, params=dict(uuid=notification["id"]), data=dict(dismissed=True)
        )

        self.assertEqual(True, pjson["dismissed"])

    def test_org_notifications(self):
        # Create a notification on the organization.
        org = model.user.get_user_or_org(ORGANIZATION)
        model.notification.create_notification("test_notification", org, {"org": "notification"})

        # Ensure it is visible to the org admin.
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(UserNotificationList)
        notification = json["notifications"][0]

        self.assertEqual(notification["kind"], "test_notification")
        self.assertEqual(notification["metadata"], {"org": "notification"})

        # Ensure it is not visible to an org member.
        self.login(READ_ACCESS_USER)
        json = self.getJsonResponse(UserNotificationList)
        self.assertEqual(0, len(json["notifications"]))


class TestGetUserPrivateAllowed(ApiTestCase):
    def test_nonallowed(self):
        self.login(READ_ACCESS_USER)
        json = self.getJsonResponse(PrivateRepositories)
        assert json["privateCount"] == 0
        assert not json["privateAllowed"]

    def test_allowed(self):
        self.login(ADMIN_ACCESS_USER)

        # Change the subscription of the namespace.
        self.putJsonResponse(UserPlan, data=dict(plan="personal-2018"))

        json = self.getJsonResponse(PrivateRepositories)
        assert json["privateCount"] >= 6
        assert not json["privateAllowed"]

        # Change the subscription of the namespace.
        self.putJsonResponse(UserPlan, data=dict(plan="bus-large-2018"))

        json = self.getJsonResponse(PrivateRepositories)
        assert json["privateAllowed"]


class TestConvertToOrganization(ApiTestCase):
    def test_sameadminuser(self):
        self.login(READ_ACCESS_USER)
        json = self.postJsonResponse(
            ConvertToOrganization,
            data={"adminUser": READ_ACCESS_USER, "adminPassword": "password", "plan": "free"},
            expected_code=400,
        )

        self.assertEqual("The admin user is not valid", json["detail"])

    def test_sameadminuser_by_email(self):
        self.login(READ_ACCESS_USER)
        json = self.postJsonResponse(
            ConvertToOrganization,
            data={"adminUser": "no1@thanks.com", "adminPassword": "password", "plan": "free"},
            expected_code=400,
        )

        self.assertEqual("The admin user is not valid", json["detail"])

    def test_invalidadminuser(self):
        self.login(READ_ACCESS_USER)
        json = self.postJsonResponse(
            ConvertToOrganization,
            data={"adminUser": "unknownuser", "adminPassword": "password", "plan": "free"},
            expected_code=400,
        )

        self.assertEqual("The admin user credentials are not valid", json["detail"])

    def test_invalidadminpassword(self):
        self.login(READ_ACCESS_USER)
        json = self.postJsonResponse(
            ConvertToOrganization,
            data={"adminUser": ADMIN_ACCESS_USER, "adminPassword": "invalidpass", "plan": "free"},
            expected_code=400,
        )

        self.assertEqual("The admin user credentials are not valid", json["detail"])

    def test_convert(self):
        self.login(READ_ACCESS_USER)

        # Add at least one permission for the read-user.
        read_user = model.user.get_user(READ_ACCESS_USER)
        simple_repo = model.repository.get_repository(ADMIN_ACCESS_USER, "simple")
        read_role = database.Role.get(name="read")

        database.RepositoryPermission.create(user=read_user, repository=simple_repo, role=read_role)

        # Convert the read user into an organization.
        json = self.postJsonResponse(
            ConvertToOrganization,
            data={"adminUser": ADMIN_ACCESS_USER, "adminPassword": "password", "plan": "free"},
        )

        self.assertEqual(True, json["success"])

        # Verify the organization exists.
        organization = model.organization.get_organization(READ_ACCESS_USER)
        assert organization is not None

        # Verify the admin user is the org's admin.
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(Organization, params=dict(orgname=READ_ACCESS_USER))

        self.assertEqual(READ_ACCESS_USER, json["name"])
        self.assertEqual(True, json["is_admin"])

        # Verify the now-org has no permissions.
        count = (
            database.RepositoryPermission.select()
            .where(database.RepositoryPermission.user == organization)
            .count()
        )
        self.assertEqual(0, count)

    def test_convert_via_email(self):
        self.login(READ_ACCESS_USER)
        json = self.postJsonResponse(
            ConvertToOrganization,
            data={"adminUser": ADMIN_ACCESS_EMAIL, "adminPassword": "password", "plan": "free"},
        )

        self.assertEqual(True, json["success"])

        # Verify the organization exists.
        organization = model.organization.get_organization(READ_ACCESS_USER)
        assert organization is not None

        # Verify the admin user is the org's admin.
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(Organization, params=dict(orgname=READ_ACCESS_USER))

        self.assertEqual(READ_ACCESS_USER, json["name"])
        self.assertEqual(True, json["is_admin"])


class TestChangeUserDetails(ApiTestCase):
    def test_changepassword(self):
        self.login(READ_ACCESS_USER)
        self.putJsonResponse(User, data=dict(password="newpasswordiscool"))
        self.login(READ_ACCESS_USER, password="newpasswordiscool")

    def test_changepassword_unicode(self):
        self.login(READ_ACCESS_USER)
        self.putJsonResponse(User, data=dict(password="someunicode北京市pass"))
        self.login(READ_ACCESS_USER, password="someunicode北京市pass")

    def test_changeeemail(self):
        self.login(READ_ACCESS_USER)

        self.putJsonResponse(User, data=dict(email="test+foo@devtable.com"))

    def test_changeinvoiceemail(self):
        self.login(READ_ACCESS_USER)

        json = self.putJsonResponse(User, data=dict(invoice_email=True))
        self.assertEqual(True, json["invoice_email"])

        json = self.putJsonResponse(User, data=dict(invoice_email=False))
        self.assertEqual(False, json["invoice_email"])

    def test_changeusername_temp(self):
        self.login(READ_ACCESS_USER)
        user = model.user.get_user(READ_ACCESS_USER)
        model.user.create_user_prompt(user, "confirm_username")
        self.assertTrue(model.user.has_user_prompt(user, "confirm_username"))

        # Add a robot under the user's namespace.
        model.user.create_robot("somebot", user)

        # Rename the user.
        json = self.putJsonResponse(User, data=dict(username="someotherusername"))

        # Ensure the username was changed.
        self.assertEqual("someotherusername", json["username"])
        self.assertFalse(model.user.has_user_prompt(user, "confirm_username"))

        # Ensure the robot was changed.
        self.assertIsNone(model.user.get_user(READ_ACCESS_USER + "+somebot"))
        self.assertIsNotNone(model.user.get_user("someotherusername+somebot"))

    def test_changeusername_temp_samename(self):
        self.login(READ_ACCESS_USER)
        user = model.user.get_user(READ_ACCESS_USER)
        model.user.create_user_prompt(user, "confirm_username")
        self.assertTrue(model.user.has_user_prompt(user, "confirm_username"))

        json = self.putJsonResponse(User, data=dict(username=READ_ACCESS_USER))

        # Ensure the username was not changed but they are no longer temporarily named.
        self.assertEqual(READ_ACCESS_USER, json["username"])
        self.assertFalse(model.user.has_user_prompt(user, "confirm_username"))

    def test_changeusername_notallowed(self):
        with self.toggleFeature("USER_RENAME", False):
            self.login(ADMIN_ACCESS_USER)
            user = model.user.get_user(ADMIN_ACCESS_USER)
            self.assertFalse(model.user.has_user_prompt(user, "confirm_username"))

            json = self.putJsonResponse(User, data=dict(username="someotherusername"))
            self.assertEqual(ADMIN_ACCESS_USER, json["username"])
            self.assertTrue("prompts" in json)

            self.assertIsNone(model.user.get_user("someotherusername"))
            self.assertIsNotNone(model.user.get_user(ADMIN_ACCESS_USER))

    def test_changeusername_allowed(self):
        with self.toggleFeature("USER_RENAME", True):
            self.login(ADMIN_ACCESS_USER)
            user = model.user.get_user(ADMIN_ACCESS_USER)
            self.assertFalse(model.user.has_user_prompt(user, "confirm_username"))

            json = self.putJsonResponse(User, data=dict(username="someotherusername"))
            self.assertEqual("someotherusername", json["username"])
            self.assertTrue("prompts" in json)

            self.assertIsNotNone(model.user.get_user("someotherusername"))
            self.assertIsNone(model.user.get_user(ADMIN_ACCESS_USER))

    def test_changeusername_already_used(self):
        self.login(READ_ACCESS_USER)
        user = model.user.get_user(READ_ACCESS_USER)
        model.user.create_user_prompt(user, "confirm_username")
        self.assertTrue(model.user.has_user_prompt(user, "confirm_username"))

        # Try to change to a used username.
        self.putJsonResponse(User, data=dict(username=ADMIN_ACCESS_USER), expected_code=400)

        # Change to a new username.
        self.putJsonResponse(User, data=dict(username="unusedusername"))


class TestCreateNewUser(ApiTestCase):
    def test_existingusername(self):
        json = self.postJsonResponse(
            User,
            data=dict(username=READ_ACCESS_USER, password="password", email="test@example.com"),
            expected_code=400,
        )

        self.assertEqual("The username already exists", json["detail"])

    def test_trycreatetooshort(self):
        json = self.postJsonResponse(
            User,
            data=dict(username="a", password="password", email="test@example.com"),
            expected_code=400,
        )

        self.assertEqual(
            "Invalid namespace a: Namespace must be between 2 and 255 characters in length",
            json["detail"],
        )

    def test_trycreateregexmismatch(self):
        json = self.postJsonResponse(
            User,
            data=dict(username="auserName", password="password", email="test@example.com"),
            expected_code=400,
        )

        self.assertEqual(
            "Invalid namespace auserName: Namespace must match expression ^([a-z0-9]+(?:[._-][a-z0-9]+)*)$",
            json["detail"],
        )

    def test_createuser(self):
        data = self.postJsonResponse(User, data=NEW_USER_DETAILS, expected_code=200)
        self.assertEqual(True, data["awaiting_verification"])

    def test_createuser_captcha(self):
        @urlmatch(netloc=r"(.*\.)?google.com", path="/recaptcha/api/siteverify")
        def captcha_endpoint(url, request):
            if url.query.find("response=somecode") > 0:
                return {"status_code": 200, "content": py_json.dumps({"success": True})}
            else:
                return {"status_code": 400, "content": py_json.dumps({"success": False})}

        with HTTMock(captcha_endpoint):
            with self.toggleFeature("RECAPTCHA", True):
                # Try with a missing captcha.
                self.postResponse(User, data=NEW_USER_DETAILS, expected_code=400)

                # Try with an invalid captcha.
                details = dict(NEW_USER_DETAILS)
                details["recaptcha_response"] = "someinvalidcode"
                self.postResponse(User, data=details, expected_code=400)

                # Try with a valid captcha.
                details = dict(NEW_USER_DETAILS)
                details["recaptcha_response"] = "somecode"
                self.postResponse(User, data=details, expected_code=200)

    def test_recaptcha_whitelisted_users(self):
        self.login(READ_ACCESS_USER)
        with (self.toggleFeature("RECAPTCHA", True)):
            app.config["RECAPTCHA_WHITELISTED_USERS"] = READ_ACCESS_USER
            self.postResponse(User, data=NEW_USER_DETAILS, expected_code=200)

    def test_createuser_withteaminvite(self):
        inviter = model.user.get_user(ADMIN_ACCESS_USER)
        team = model.team.get_organization_team(ORGANIZATION, "owners")
        invite = model.team.add_or_invite_to_team(inviter, team, None, NEW_USER_DETAILS["email"])

        details = {"invite_code": invite.invite_token}
        details.update(NEW_USER_DETAILS)

        data = self.postJsonResponse(User, data=details, expected_code=200)

        # Make sure the user is verified since the email address of the user matches
        # that of the team invite.
        self.assertFalse("awaiting_verification" in data)

        # Make sure the user was not (yet) added to the team.
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname=ORGANIZATION, teamname="owners")
        )
        self.assertNotInTeam(json, NEW_USER_DETAILS["username"])

    def test_createuser_withteaminvite_differentemails(self):
        inviter = model.user.get_user(ADMIN_ACCESS_USER)
        team = model.team.get_organization_team(ORGANIZATION, "owners")
        invite = model.team.add_or_invite_to_team(inviter, team, None, "differentemail@example.com")

        details = {"invite_code": invite.invite_token}
        details.update(NEW_USER_DETAILS)

        data = self.postJsonResponse(User, data=details, expected_code=200)

        # Make sure the user is *not* verified since the email address of the user
        # does not match that of the team invite.
        self.assertTrue(data["awaiting_verification"])

        # Make sure the user was not (yet) added to the team.
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname=ORGANIZATION, teamname="owners")
        )
        self.assertNotInTeam(json, NEW_USER_DETAILS["username"])

    def test_createuser_withmultipleteaminvites(self):
        inviter = model.user.get_user(ADMIN_ACCESS_USER)
        owners_team = model.team.get_organization_team(ORGANIZATION, "owners")
        readers_team = model.team.get_organization_team(ORGANIZATION, "readers")
        other_owners_team = model.team.get_organization_team("library", "owners")

        owners_invite = model.team.add_or_invite_to_team(
            inviter, owners_team, None, NEW_USER_DETAILS["email"]
        )

        readers_invite = model.team.add_or_invite_to_team(
            inviter, readers_team, None, NEW_USER_DETAILS["email"]
        )

        other_owners_invite = model.team.add_or_invite_to_team(
            inviter, other_owners_team, None, NEW_USER_DETAILS["email"]
        )

        # Create the user and ensure they have a verified email address.
        details = {"invite_code": owners_invite.invite_token}
        details.update(NEW_USER_DETAILS)

        data = self.postJsonResponse(User, data=details, expected_code=200)

        # Make sure the user is verified since the email address of the user matches
        # that of the team invite.
        self.assertFalse("awaiting_verification" in data)

        # Make sure the user was not (yet) added to the teams.
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname=ORGANIZATION, teamname="owners")
        )
        self.assertNotInTeam(json, NEW_USER_DETAILS["username"])

        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname=ORGANIZATION, teamname="readers")
        )
        self.assertNotInTeam(json, NEW_USER_DETAILS["username"])

        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname="library", teamname="owners")
        )
        self.assertNotInTeam(json, NEW_USER_DETAILS["username"])

        # Accept the first invitation.
        self.login(NEW_USER_DETAILS["username"])
        self.putJsonResponse(TeamMemberInvite, params=dict(code=owners_invite.invite_token))

        # Make sure both codes are now invalid.
        self.putResponse(
            TeamMemberInvite, params=dict(code=owners_invite.invite_token), expected_code=400
        )

        self.putResponse(
            TeamMemberInvite, params=dict(code=readers_invite.invite_token), expected_code=400
        )

        # Make sure the user is now in the two invited teams under the organization, but not
        # in the other org's team.
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname=ORGANIZATION, teamname="owners")
        )
        self.assertInTeam(json, NEW_USER_DETAILS["username"])

        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname=ORGANIZATION, teamname="readers")
        )
        self.assertInTeam(json, NEW_USER_DETAILS["username"])

        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname="library", teamname="owners")
        )
        self.assertNotInTeam(json, NEW_USER_DETAILS["username"])

        # Accept the second invitation.
        self.login(NEW_USER_DETAILS["username"])
        self.putJsonResponse(TeamMemberInvite, params=dict(code=other_owners_invite.invite_token))

        # Make sure the user was added to the other organization.
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname="library", teamname="owners")
        )
        self.assertInTeam(json, NEW_USER_DETAILS["username"])

        # Make sure the invitation codes are now invalid.
        self.putResponse(
            TeamMemberInvite, params=dict(code=other_owners_invite.invite_token), expected_code=400
        )


class TestDeleteNamespace(ApiTestCase):
    def test_deletenamespaces(self):
        self.login(ADMIN_ACCESS_USER)

        # Try to first delete the user. Since they are the sole admin of three orgs, it should fail.
        with check_transitive_modifications():
            self.deleteResponse(User, expected_code=400)

        # Delete the three orgs, checking in between.
        with check_transitive_modifications():
            self.deleteEmptyResponse(
                Organization, params=dict(orgname=ORGANIZATION), expected_code=204
            )
            self.deleteResponse(User, expected_code=400)  # Should still fail.
            self.deleteEmptyResponse(
                Organization, params=dict(orgname="library"), expected_code=204
            )
            self.deleteResponse(User, expected_code=400)  # Should still fail.
            self.deleteEmptyResponse(Organization, params=dict(orgname="titi"), expected_code=204)
            self.deleteEmptyResponse(
                Organization, params=dict(orgname="proxyorg"), expected_code=204
            )

        # Add some queue items for the user.
        notification_queue.put([ADMIN_ACCESS_USER, "somerepo", "somename"], "{}")
        dockerfile_build_queue.put([ADMIN_ACCESS_USER, "anotherrepo"], "{}")

        # Now delete the user.
        with check_transitive_modifications():
            self.deleteEmptyResponse(User, expected_code=204)

        # Ensure the queue items are gone.
        self.assertIsNone(notification_queue.get())
        self.assertIsNone(dockerfile_build_queue.get())

    def test_delete_federateduser(self):
        self.login(PUBLIC_USER)

        # Add some federated logins.
        user = model.user.get_user(PUBLIC_USER)
        model.user.attach_federated_login(user, "github", "something", {})

        with check_transitive_modifications():
            self.deleteEmptyResponse(User, expected_code=204)

    def test_delete_prompted_user(self):
        self.login("randomuser")
        with check_transitive_modifications():
            self.deleteEmptyResponse(User, expected_code=204)


class TestSignin(ApiTestCase):
    def test_signin_unicode(self):
        self.postResponse(
            Signin,
            data=dict(username="\xe5\x8c\x97\xe4\xba\xac\xe5\xb8\x82", password="password"),
            expected_code=403,
        )

    def test_signin_invitecode(self):
        # Create a new user (unverified)
        data = self.postJsonResponse(User, data=NEW_USER_DETAILS, expected_code=200)
        self.assertTrue(data["awaiting_verification"])

        # Try to sign in without an invite code.
        data = self.postJsonResponse(Signin, data=NEW_USER_DETAILS, expected_code=403)
        self.assertTrue(data["needsEmailVerification"])

        # Try to sign in with an invalid invite code.
        details = {"invite_code": "someinvalidcode"}
        details.update(NEW_USER_DETAILS)

        data = self.postJsonResponse(Signin, data=details, expected_code=403)
        self.assertTrue(data["needsEmailVerification"])

        # Sign in with an invite code and ensure the user becomes verified.
        inviter = model.user.get_user(ADMIN_ACCESS_USER)
        team = model.team.get_organization_team(ORGANIZATION, "owners")
        invite = model.team.add_or_invite_to_team(inviter, team, None, NEW_USER_DETAILS["email"])

        details = {"invite_code": invite.invite_token}
        details.update(NEW_USER_DETAILS)

        data = self.postJsonResponse(Signin, data=details, expected_code=200)
        self.assertFalse("needsEmailVerification" in data)


class TestSignout(ApiTestCase):
    def test_signout(self):
        self.login(READ_ACCESS_USER)

        read_user = model.user.get_user(READ_ACCESS_USER)
        json = self.getJsonResponse(User)
        assert json["username"] == READ_ACCESS_USER

        self.postResponse(Signout)

        # Make sure we're now signed out.
        self.getJsonResponse(User, expected_code=401)

        # Make sure the user's UUID has rotated, to ensure sessions are no longer valid.
        read_user_again = model.user.get_user(READ_ACCESS_USER)
        self.assertNotEqual(read_user.uuid, read_user_again.uuid)


class TestConductSearch(ApiTestCase):
    def test_noaccess(self):
        self.login(NO_ACCESS_USER)

        json = self.getJsonResponse(ConductSearch, params=dict(query="read"))

        self.assertEqual(0, len(json["results"]))

        json = self.getJsonResponse(ConductSearch, params=dict(query="owners"))

        self.assertEqual(0, len(json["results"]))

    def test_nouser(self):
        json = self.getJsonResponse(ConductSearch, params=dict(query="read"))

        self.assertEqual(0, len(json["results"]))

        json = self.getJsonResponse(ConductSearch, params=dict(query="public"))

        self.assertEqual(2, len(json["results"]))
        self.assertEqual(json["results"][0]["kind"], "repository")
        self.assertEqual(json["results"][0]["name"], "publicrepo")

        self.assertEqual(json["results"][1]["kind"], "user")
        self.assertEqual(json["results"][1]["name"], "public")

        json = self.getJsonResponse(ConductSearch, params=dict(query="owners"))

        self.assertEqual(0, len(json["results"]))

    def test_orgmember(self):
        self.login(READ_ACCESS_USER)

        json = self.getJsonResponse(ConductSearch, params=dict(query="owners"))

        self.assertEqual(0, len(json["results"]))

        json = self.getJsonResponse(ConductSearch, params=dict(query="readers"))

        self.assertEqual(1, len(json["results"]))
        self.assertEqual(json["results"][0]["kind"], "team")
        self.assertEqual(json["results"][0]["name"], "readers")

    def test_orgadmin(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(ConductSearch, params=dict(query="owners"))

        self.assertEqual(5, len(json["results"]))
        self.assertEqual(json["results"][0]["kind"], "team")
        self.assertEqual(json["results"][0]["name"], "owners")

        json = self.getJsonResponse(ConductSearch, params=dict(query="readers"))

        self.assertEqual(1, len(json["results"]))
        self.assertEqual(json["results"][0]["kind"], "team")
        self.assertEqual(json["results"][0]["name"], "readers")

    def test_explicit_permission(self):
        self.login("reader")

        json = self.getJsonResponse(ConductSearch, params=dict(query="shared"))

        self.assertEqual(1, len(json["results"]))
        self.assertEqual(json["results"][0]["kind"], "repository")
        self.assertEqual(json["results"][0]["name"], "shared")

    def test_full_text(self):
        self.login(ADMIN_ACCESS_USER)

        # Make sure the repository is found via `full` and `text search`.
        json = self.getJsonResponse(ConductSearch, params=dict(query="full"))
        self.assertEqual(1, len(json["results"]))
        self.assertEqual(json["results"][0]["kind"], "repository")
        self.assertEqual(json["results"][0]["name"], "text-full-repo")

        json = self.getJsonResponse(ConductSearch, params=dict(query="text search"))
        self.assertEqual(1, len(json["results"]))
        self.assertEqual(json["results"][0]["kind"], "repository")
        self.assertEqual(json["results"][0]["name"], "text-full-repo")


class TestGetMatchingEntities(ApiTestCase):
    def test_simple_lookup(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(
            EntitySearch,
            params=dict(prefix=ADMIN_ACCESS_USER, namespace=ORGANIZATION, includeTeams="true"),
        )
        self.assertEqual(1, len(json["results"]))

    def test_simple_lookup_noorg(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(EntitySearch, params=dict(prefix=ADMIN_ACCESS_USER))
        self.assertEqual(1, len(json["results"]))

    def test_unicode_search(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(
            EntitySearch, params=dict(prefix="北京市", namespace=ORGANIZATION, includeTeams="true")
        )
        self.assertEqual(0, len(json["results"]))

    def test_notinorg(self):
        self.login(NO_ACCESS_USER)

        json = self.getJsonResponse(
            EntitySearch, params=dict(prefix="o", namespace=ORGANIZATION, includeTeams="true")
        )

        names = set([r["name"] for r in json["results"]])
        assert "outsideorg" in names
        assert not "owners" in names

    def test_prefix_disabled(self):
        with patch("features.PARTIAL_USER_AUTOCOMPLETE", False):
            self.login(NO_ACCESS_USER)

            json = self.getJsonResponse(
                EntitySearch, params=dict(prefix="o", namespace=ORGANIZATION, includeTeams="true")
            )

            names = set([r["name"] for r in json["results"]])
            assert not "outsideorg" in names
            assert not "owners" in names

            json = self.getJsonResponse(
                EntitySearch,
                params=dict(prefix="outsideorg", namespace=ORGANIZATION, includeTeams="true"),
            )
            names = set([r["name"] for r in json["results"]])
            assert "outsideorg" in names
            assert not "owners" in names

    def test_inorg(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(
            EntitySearch, params=dict(prefix="o", namespace=ORGANIZATION, includeTeams="true")
        )

        names = set([r["name"] for r in json["results"]])
        assert "outsideorg" in names
        assert "owners" in names

    def test_inorg_withorgs(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(
            EntitySearch,
            params=dict(prefix=ORGANIZATION[0], namespace=ORGANIZATION, includeOrgs="true"),
        )

        names = set([r["name"] for r in json["results"]])
        assert ORGANIZATION in names


class TestCreateOrganization(ApiTestCase):
    def test_existinguser(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.postJsonResponse(
            OrganizationList,
            data=dict(name=ADMIN_ACCESS_USER, email="testorg@example.com"),
            expected_code=400,
        )

        self.assertEqual("A user or organization with this name already exists", json["detail"])

    def test_existingorg(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.postJsonResponse(
            OrganizationList,
            data=dict(name=ORGANIZATION, email="testorg@example.com"),
            expected_code=400,
        )

        self.assertEqual("A user or organization with this name already exists", json["detail"])

    def test_createorg(self):
        self.login(ADMIN_ACCESS_USER)

        data = self.postResponse(
            OrganizationList,
            data=dict(name="neworg", email="testorg@example.com"),
            expected_code=201,
        )

        self.assertEqual(b'"Created"', data.strip())

        # Ensure the org was created.
        organization = model.organization.get_organization("neworg")
        assert organization is not None

        # Verify the admin user is the org's admin.
        json = self.getJsonResponse(Organization, params=dict(orgname="neworg"))
        self.assertEqual("neworg", json["name"])
        self.assertEqual(True, json["is_admin"])

    def test_createorg_viaoauth(self):
        # Attempt with no auth.
        self.postResponse(
            OrganizationList,
            data=dict(name="neworg", email="testorg@example.com"),
            expected_code=401,
        )

        # Attempt with auth with invalid scope.
        dt_user = model.user.get_user(ADMIN_ACCESS_USER)
        token, code = model.oauth.create_user_access_token(dt_user, "deadbeef", "repo:read")
        self.postResponse(
            OrganizationList,
            data=dict(name="neworg", email="testorg@example.com"),
            headers=dict(Authorization="Bearer " + code),
            expected_code=403,
        )

        # Create OAuth token with user:admin scope.
        token, code = model.oauth.create_user_access_token(
            dt_user, "deadbeef", "user:admin", access_token="d" * 40
        )
        data = self.postResponse(
            OrganizationList,
            data=dict(name="neworg", email="testorg@example.com"),
            headers=dict(Authorization="Bearer " + code),
            expected_code=201,
        )

        self.assertEqual(b'"Created"', data.strip())


class TestGetOrganization(ApiTestCase):
    def test_unknownorg(self):
        self.login(ADMIN_ACCESS_USER)
        self.getResponse(Organization, params=dict(orgname="notvalid"), expected_code=404)

    def test_cannotaccess(self):
        self.login(NO_ACCESS_USER)
        self.getResponse(Organization, params=dict(orgname=ORGANIZATION), expected_code=200)

    def test_getorganization(self):
        self.login(READ_ACCESS_USER)
        json = self.getJsonResponse(Organization, params=dict(orgname=ORGANIZATION))

        self.assertEqual(ORGANIZATION, json["name"])
        self.assertEqual(False, json["is_admin"])

    def test_getorganization_asadmin(self):
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(Organization, params=dict(orgname=ORGANIZATION))

        self.assertEqual(ORGANIZATION, json["name"])
        self.assertEqual(True, json["is_admin"])


class TestChangeOrganizationDetails(ApiTestCase):
    def test_changeinvoiceemail(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.putJsonResponse(
            Organization, params=dict(orgname=ORGANIZATION), data=dict(invoice_email=True)
        )

        self.assertEqual(True, json["invoice_email"])

        json = self.putJsonResponse(
            Organization, params=dict(orgname=ORGANIZATION), data=dict(invoice_email=False)
        )
        self.assertEqual(False, json["invoice_email"])

    def test_changemail(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.putJsonResponse(
            Organization, params=dict(orgname=ORGANIZATION), data=dict(email="newemail@example.com")
        )

        self.assertEqual("newemail@example.com", json["email"])


class TestGetOrganizationPrototypes(ApiTestCase):
    def test_getprototypes(self):
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION))

        assert len(json["prototypes"]) > 0


class TestCreateOrganizationPrototypes(ApiTestCase):
    def test_invaliduser(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.postJsonResponse(
            PermissionPrototypeList,
            params=dict(orgname=ORGANIZATION),
            data=dict(
                activating_user={"name": "unknownuser"},
                role="read",
                delegate={"kind": "team", "name": "owners"},
            ),
            expected_code=400,
        )

        self.assertEqual("Unknown activating user", json["detail"])

    def test_missingdelegate(self):
        self.login(ADMIN_ACCESS_USER)

        self.postJsonResponse(
            PermissionPrototypeList,
            params=dict(orgname=ORGANIZATION),
            data=dict(role="read"),
            expected_code=400,
        )

    def test_createprototype(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.postJsonResponse(
            PermissionPrototypeList,
            params=dict(orgname=ORGANIZATION),
            data=dict(role="read", delegate={"kind": "team", "name": "readers"}),
        )

        self.assertEqual("read", json["role"])
        pid = json["id"]

        # Verify the prototype exists.
        json = self.getJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION))

        ids = set([p["id"] for p in json["prototypes"]])
        assert pid in ids


class TestDeleteOrganizationPrototypes(ApiTestCase):
    def test_deleteprototype(self):
        self.login(ADMIN_ACCESS_USER)

        # Get the existing prototypes
        json = self.getJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION))

        ids = [p["id"] for p in json["prototypes"]]
        pid = ids[0]

        # Delete a prototype.
        self.deleteEmptyResponse(
            PermissionPrototype, params=dict(orgname=ORGANIZATION, prototypeid=pid)
        )

        # Verify the prototype no longer exists.
        json = self.getJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION))

        newids = [p["id"] for p in json["prototypes"]]
        assert not pid in newids


class TestUpdateOrganizationPrototypes(ApiTestCase):
    def test_updateprototype(self):
        self.login(ADMIN_ACCESS_USER)

        # Get the existing prototypes
        json = self.getJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION))

        ids = [p["id"] for p in json["prototypes"]]
        pid = ids[0]

        # Update a prototype.
        json = self.putJsonResponse(
            PermissionPrototype,
            params=dict(orgname=ORGANIZATION, prototypeid=pid),
            data=dict(role="admin"),
        )

        self.assertEqual("admin", json["role"])


class TestGetOrganizationMembers(ApiTestCase):
    def test_getmembers(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(OrganizationMemberList, params=dict(orgname=ORGANIZATION))

        membernames = [member["name"] for member in json["members"]]
        assert ADMIN_ACCESS_USER in membernames
        assert READ_ACCESS_USER in membernames
        assert not NO_ACCESS_USER in membernames

        for member in json["members"]:
            membername = member["name"]
            response = self.getJsonResponse(
                OrganizationMember, params=dict(orgname=ORGANIZATION, membername=membername)
            )

            self.assertEqual(member, response)


class TestRemoveOrganizationMember(ApiTestCase):
    def test_try_remove_only_admin(self):
        self.login(ADMIN_ACCESS_USER)

        self.deleteResponse(
            OrganizationMember,
            params=dict(orgname=ORGANIZATION, membername=ADMIN_ACCESS_USER),
            expected_code=400,
        )

    def test_remove_member(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(OrganizationMemberList, params=dict(orgname=ORGANIZATION))

        membernames = [member["name"] for member in json["members"]]
        assert ADMIN_ACCESS_USER in membernames
        assert READ_ACCESS_USER in membernames

        self.deleteEmptyResponse(
            OrganizationMember, params=dict(orgname=ORGANIZATION, membername=READ_ACCESS_USER)
        )

        json = self.getJsonResponse(OrganizationMemberList, params=dict(orgname=ORGANIZATION))

        membernames = [member["name"] for member in json["members"]]
        assert ADMIN_ACCESS_USER in membernames
        assert not READ_ACCESS_USER in membernames

    def test_remove_member_repo_permission(self):
        self.login(ADMIN_ACCESS_USER)

        # Add read user as a direct permission on the admin user's repo.
        model.permission.set_user_repo_permission(
            READ_ACCESS_USER, ADMIN_ACCESS_USER, "simple", "read"
        )

        # Verify the user has a permission on the admin user's repo.
        admin_perms = [
            p.user.username for p in model.user.get_all_repo_users(ADMIN_ACCESS_USER, "simple")
        ]
        assert READ_ACCESS_USER in admin_perms

        # Add read user as a direct permission on the org repo.
        model.permission.set_user_repo_permission(READ_ACCESS_USER, ORGANIZATION, ORG_REPO, "read")

        # Verify the user has a permission on the org repo.
        org_perms = [p.user.username for p in model.user.get_all_repo_users(ORGANIZATION, ORG_REPO)]
        assert READ_ACCESS_USER in org_perms

        # Remove the user from the org.
        self.deleteEmptyResponse(
            OrganizationMember, params=dict(orgname=ORGANIZATION, membername=READ_ACCESS_USER)
        )

        # Verify that the user's permission on the org repo is gone, but it is still
        # present on the other repo.
        org_perms = [p.user.username for p in model.user.get_all_repo_users(ORGANIZATION, ORG_REPO)]
        assert not READ_ACCESS_USER in org_perms

        admin_perms = [
            p.user.username for p in model.user.get_all_repo_users(ADMIN_ACCESS_USER, "simple")
        ]
        assert READ_ACCESS_USER in admin_perms


class TestGetOrganizationPrivateAllowed(ApiTestCase):
    def test_existingorg(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(OrgPrivateRepositories, params=dict(orgname=ORGANIZATION))

        self.assertEqual(True, json["privateAllowed"])
        assert not "reposAllowed" in json

    def test_neworg(self):
        self.login(ADMIN_ACCESS_USER)

        data = self.postResponse(
            OrganizationList, data=dict(name="neworg", email="test@example.com"), expected_code=201
        )

        json = self.getJsonResponse(OrgPrivateRepositories, params=dict(orgname="neworg"))

        self.assertEqual(False, json["privateAllowed"])


class TestUpdateOrganizationTeam(ApiTestCase):
    def test_updateexisting(self):
        self.login(ADMIN_ACCESS_USER)

        data = self.putJsonResponse(
            OrganizationTeam,
            params=dict(orgname=ORGANIZATION, teamname="readers"),
            data=dict(description="My cool team", role="creator"),
        )

        self.assertEqual("My cool team", data["description"])
        self.assertEqual("creator", data["role"])

    def test_attemptchangeroleonowners(self):
        self.login(ADMIN_ACCESS_USER)

        self.putJsonResponse(
            OrganizationTeam,
            params=dict(orgname=ORGANIZATION, teamname="owners"),
            data=dict(role="creator"),
            expected_code=400,
        )

    def test_createnewteam(self):
        self.login(ADMIN_ACCESS_USER)

        data = self.putJsonResponse(
            OrganizationTeam,
            params=dict(orgname=ORGANIZATION, teamname="newteam"),
            data=dict(description="My cool team", role="member"),
        )

        self.assertEqual("My cool team", data["description"])
        self.assertEqual("member", data["role"])

        # Verify the team was created.
        json = self.getJsonResponse(Organization, params=dict(orgname=ORGANIZATION))
        assert "newteam" in json["teams"]


class TestDeleteOrganizationTeam(ApiTestCase):
    def test_deleteteam(self):
        self.login(ADMIN_ACCESS_USER)

        self.deleteEmptyResponse(
            OrganizationTeam, params=dict(orgname=ORGANIZATION, teamname="readers")
        )

        # Make sure the team was deleted
        json = self.getJsonResponse(Organization, params=dict(orgname=ORGANIZATION))
        assert not "readers" in json["teams"]

    def test_attemptdeleteowners(self):
        self.login(ADMIN_ACCESS_USER)

        resp = self.deleteResponse(
            OrganizationTeam,
            params=dict(orgname=ORGANIZATION, teamname="owners"),
            expected_code=400,
        )
        data = py_json.loads(resp)
        msg = (
            "Deleting team 'owners' would remove admin ability for user "
            + "'devtable' in organization 'buynlarge'"
        )
        self.assertEqual(msg, data["message"])


class TestTeamPermissions(ApiTestCase):
    def test_team_permissions(self):
        self.login(ADMIN_ACCESS_USER)

        resp = self.getJsonResponse(
            TeamPermissions, params=dict(orgname=ORGANIZATION, teamname="readers")
        )

        self.assertEqual(1, len(resp["permissions"]))


class TestGetOrganizationTeamMembers(ApiTestCase):
    def test_invalidteam(self):
        self.login(ADMIN_ACCESS_USER)

        self.getResponse(
            TeamMemberList,
            params=dict(orgname=ORGANIZATION, teamname="notvalid"),
            expected_code=404,
        )

    def test_getmembers(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname=ORGANIZATION, teamname="readers")
        )

        self.assertInTeam(json, READ_ACCESS_USER)


class TestUpdateOrganizationTeamMember(ApiTestCase):
    def test_addmember_alreadyteammember(self):
        self.login(ADMIN_ACCESS_USER)

        membername = READ_ACCESS_USER
        self.putResponse(
            TeamMember,
            params=dict(orgname=ORGANIZATION, teamname="readers", membername=membername),
            expected_code=400,
        )

    def test_addmember_orgmember(self):
        self.login(ADMIN_ACCESS_USER)

        membername = READ_ACCESS_USER
        self.putJsonResponse(
            TeamMember, params=dict(orgname=ORGANIZATION, teamname="owners", membername=membername)
        )

        # Verify the user was added to the team.
        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname=ORGANIZATION, teamname="owners")
        )

        self.assertInTeam(json, membername)

    def test_addmember_robot(self):
        self.login(ADMIN_ACCESS_USER)

        membername = ORGANIZATION + "+coolrobot"
        self.putJsonResponse(
            TeamMember, params=dict(orgname=ORGANIZATION, teamname="readers", membername=membername)
        )

        # Verify the user was added to the team.
        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname=ORGANIZATION, teamname="readers")
        )

        self.assertInTeam(json, membername)

    def test_addmember_invalidrobot(self):
        self.login(ADMIN_ACCESS_USER)

        membername = "freshuser+anotherrobot"
        self.putResponse(
            TeamMember,
            params=dict(orgname=ORGANIZATION, teamname="readers", membername=membername),
            expected_code=400,
        )

    def test_addmember_nonorgmember(self):
        self.login(ADMIN_ACCESS_USER)

        membername = NO_ACCESS_USER
        response = self.putJsonResponse(
            TeamMember, params=dict(orgname=ORGANIZATION, teamname="owners", membername=membername)
        )

        self.assertEqual(True, response["invited"])

        # Make sure the user is not (yet) part of the team.
        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname=ORGANIZATION, teamname="readers")
        )

        for member in json["members"]:
            self.assertNotEqual(membername, member["name"])

    def test_updatemembers_syncedteam(self):
        self.login(ADMIN_ACCESS_USER)

        with patch("endpoints.api.team.authentication", AttrDict({"federated_service": "foobar"})):
            # Add the user to a non-synced team, which should succeed.
            self.putJsonResponse(
                TeamMember,
                params=dict(orgname=ORGANIZATION, teamname="owners", membername=READ_ACCESS_USER),
            )

            # Remove the user from the non-synced team, which should succeed.
            self.deleteEmptyResponse(
                TeamMember,
                params=dict(orgname=ORGANIZATION, teamname="owners", membername=READ_ACCESS_USER),
            )

            # Attempt to add the user to a synced team, which should fail.
            self.putResponse(
                TeamMember,
                params=dict(orgname=ORGANIZATION, teamname="synced", membername=READ_ACCESS_USER),
                expected_code=400,
            )

            # Attempt to remove the user from the synced team, which should fail.
            self.deleteResponse(
                TeamMember,
                params=dict(orgname=ORGANIZATION, teamname="synced", membername=READ_ACCESS_USER),
                expected_code=400,
            )

            # Add a robot to the synced team, which should succeed.
            self.putJsonResponse(
                TeamMember,
                params=dict(
                    orgname=ORGANIZATION, teamname="synced", membername=ORGANIZATION + "+coolrobot"
                ),
            )

            # Remove the robot from the non-synced team, which should succeed.
            self.deleteEmptyResponse(
                TeamMember,
                params=dict(
                    orgname=ORGANIZATION, teamname="synced", membername=ORGANIZATION + "+coolrobot"
                ),
            )

            # Invite a team member to a non-synced team, which should succeed.
            self.putJsonResponse(
                InviteTeamMember,
                params=dict(
                    orgname=ORGANIZATION, teamname="owners", email="someguy+new@devtable.com"
                ),
            )

            # Attempt to invite a team member to a synced team, which should fail.
            self.putResponse(
                InviteTeamMember,
                params=dict(
                    orgname=ORGANIZATION, teamname="synced", email="someguy+new@devtable.com"
                ),
                expected_code=400,
            )


class TestAcceptTeamMemberInvite(ApiTestCase):
    def test_accept(self):
        self.login(ADMIN_ACCESS_USER)

        # Create the invite.
        membername = NO_ACCESS_USER
        response = self.putJsonResponse(
            TeamMember, params=dict(orgname=ORGANIZATION, teamname="owners", membername=membername)
        )

        self.assertEqual(True, response["invited"])

        # Login as the user.
        self.login(membername)

        # Accept the invite.
        user = model.user.get_user(membername)
        invites = list(model.team.lookup_team_invites(user))
        self.assertEqual(1, len(invites))

        self.putJsonResponse(TeamMemberInvite, params=dict(code=invites[0].invite_token))

        # Verify the user is now on the team.
        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname=ORGANIZATION, teamname="owners")
        )

        self.assertInTeam(json, membername)

        # Verify the accept now fails.
        self.putResponse(
            TeamMemberInvite, params=dict(code=invites[0].invite_token), expected_code=400
        )

    def test_accept_via_email(self):
        self.login(ADMIN_ACCESS_USER)

        # Create the invite.
        member = model.user.get_user(NO_ACCESS_USER)
        response = self.putJsonResponse(
            InviteTeamMember,
            params=dict(orgname=ORGANIZATION, teamname="owners", email=member.email),
        )

        self.assertEqual(True, response["invited"])

        # Login as the user.
        self.login(member.username)

        # Accept the invite.
        invites = list(model.team.lookup_team_invites_by_email(member.email))
        self.assertEqual(1, len(invites))

        self.putJsonResponse(TeamMemberInvite, params=dict(code=invites[0].invite_token))

        # Verify the user is now on the team.
        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname=ORGANIZATION, teamname="owners")
        )

        self.assertInTeam(json, member.username)

        # Verify the accept now fails.
        self.putResponse(
            TeamMemberInvite, params=dict(code=invites[0].invite_token), expected_code=400
        )

    def test_accept_invite_different_user(self):
        self.login(ADMIN_ACCESS_USER)

        # Create the invite.
        response = self.putJsonResponse(
            TeamMember,
            params=dict(orgname=ORGANIZATION, teamname="owners", membername=NO_ACCESS_USER),
        )

        self.assertEqual(True, response["invited"])

        # Login as a different user.
        self.login(PUBLIC_USER)

        # Try to accept the invite.
        user = model.user.get_user(NO_ACCESS_USER)
        invites = list(model.team.lookup_team_invites(user))
        self.assertEqual(1, len(invites))

        self.putResponse(
            TeamMemberInvite, params=dict(code=invites[0].invite_token), expected_code=400
        )

        # Ensure the invite is still valid.
        user = model.user.get_user(NO_ACCESS_USER)
        invites = list(model.team.lookup_team_invites(user))
        self.assertEqual(1, len(invites))

        # Ensure the user is *not* a member of the team.
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname=ORGANIZATION, teamname="owners")
        )
        self.assertNotInTeam(json, PUBLIC_USER)

    def test_accept_invite_different_email(self):
        self.login(ADMIN_ACCESS_USER)

        # Create the invite.
        response = self.putJsonResponse(
            InviteTeamMember,
            params=dict(orgname=ORGANIZATION, teamname="owners", email="someemail@example.com"),
        )

        self.assertEqual(True, response["invited"])

        # Login as a different user.
        self.login(PUBLIC_USER)

        # Try to accept the invite.
        invites = list(model.team.lookup_team_invites_by_email("someemail@example.com"))
        self.assertEqual(1, len(invites))

        self.putResponse(
            TeamMemberInvite, params=dict(code=invites[0].invite_token), expected_code=400
        )

        # Ensure the invite is still valid.
        invites = list(model.team.lookup_team_invites_by_email("someemail@example.com"))
        self.assertEqual(1, len(invites))

        # Ensure the user is *not* a member of the team.
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname=ORGANIZATION, teamname="owners")
        )
        self.assertNotInTeam(json, PUBLIC_USER)


class TestDeclineTeamMemberInvite(ApiTestCase):
    def test_decline_wronguser(self):
        self.login(ADMIN_ACCESS_USER)

        # Create the invite.
        membername = NO_ACCESS_USER
        response = self.putJsonResponse(
            TeamMember, params=dict(orgname=ORGANIZATION, teamname="owners", membername=membername)
        )

        self.assertEqual(True, response["invited"])

        # Try to decline the invite.
        user = model.user.get_user(membername)
        invites = list(model.team.lookup_team_invites(user))
        self.assertEqual(1, len(invites))

        self.deleteResponse(
            TeamMemberInvite, params=dict(code=invites[0].invite_token), expected_code=400
        )

    def test_decline(self):
        self.login(ADMIN_ACCESS_USER)

        # Create the invite.
        membername = NO_ACCESS_USER
        response = self.putJsonResponse(
            TeamMember, params=dict(orgname=ORGANIZATION, teamname="owners", membername=membername)
        )

        self.assertEqual(True, response["invited"])

        # Login as the user.
        self.login(membername)

        # Decline the invite.
        user = model.user.get_user(membername)
        invites = list(model.team.lookup_team_invites(user))
        self.assertEqual(1, len(invites))

        self.deleteEmptyResponse(TeamMemberInvite, params=dict(code=invites[0].invite_token))

        # Make sure the invite was deleted.
        self.deleteResponse(
            TeamMemberInvite, params=dict(code=invites[0].invite_token), expected_code=400
        )


class TestDeleteOrganizationTeamMember(ApiTestCase):
    def test_deletememberinvite(self):
        self.login(ADMIN_ACCESS_USER)

        # Verify the initial member count
        json = self.getJsonResponse(
            TeamMemberList,
            params=dict(orgname=ORGANIZATION, teamname="readers", includePending=True),
        )

        self.assertEqual(len(json["members"]), 3)

        membername = NO_ACCESS_USER
        response = self.putJsonResponse(
            TeamMember, params=dict(orgname=ORGANIZATION, teamname="readers", membername=membername)
        )

        self.assertEqual(True, response["invited"])

        # Verify the invite was added.
        json = self.getJsonResponse(
            TeamMemberList,
            params=dict(orgname=ORGANIZATION, teamname="readers", includePending=True),
        )

        self.assertEqual(len(json["members"]), 4)

        # Delete the invite.
        self.deleteEmptyResponse(
            TeamMember, params=dict(orgname=ORGANIZATION, teamname="readers", membername=membername)
        )

        # Verify the user was removed from the team.
        json = self.getJsonResponse(
            TeamMemberList,
            params=dict(orgname=ORGANIZATION, teamname="readers", includePending=True),
        )

        self.assertEqual(len(json["members"]), 3)

    def test_deletemember(self):
        self.login(ADMIN_ACCESS_USER)

        self.deleteEmptyResponse(
            TeamMember,
            params=dict(orgname=ORGANIZATION, teamname="readers", membername=READ_ACCESS_USER),
        )

        # Verify the user was removed from the team.
        json = self.getJsonResponse(
            TeamMemberList, params=dict(orgname=ORGANIZATION, teamname="readers")
        )

        self.assertEqual(len(json["members"]), 1)


class TestCreateRepo(ApiTestCase):
    def test_invalidreponame(self):
        self.login(ADMIN_ACCESS_USER)

        if app.config["FEATURE_EXTENDED_REPOSITORY_NAMES"]:
            json = self.postJsonResponse(
                RepositoryList,
                data=dict(repository="some/repo", visibility="public", description=""),
                expected_code=201,
            )
        else:
            json = self.postJsonResponse(
                RepositoryList,
                data=dict(repository="some/repo", visibility="public", description=""),
                expected_code=400,
            )
            self.assertEqual("Invalid repository name", json["detail"])

    def test_duplicaterepo(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.postJsonResponse(
            RepositoryList,
            data=dict(repository="simple", visibility="public", description=""),
            expected_code=400,
        )

        self.assertEqual("Repository already exists", json["detail"])

    def test_createrepo(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.postJsonResponse(
            RepositoryList,
            data=dict(repository="newrepo", visibility="public", description=""),
            expected_code=201,
        )

        self.assertEqual(ADMIN_ACCESS_USER, json["namespace"])
        self.assertEqual("newrepo", json["name"])

    def test_create_app_repo(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.postJsonResponse(
            RepositoryList,
            data=dict(
                repository="newrepo", visibility="public", description="", repo_kind="application"
            ),
            expected_code=201,
        )

        self.assertEqual(ADMIN_ACCESS_USER, json["namespace"])
        self.assertEqual("newrepo", json["name"])
        self.assertEqual("application", json["kind"])

    def test_createrepo_underorg(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.postJsonResponse(
            RepositoryList,
            data=dict(
                namespace=ORGANIZATION, repository="newrepo", visibility="private", description=""
            ),
            expected_code=201,
        )

        self.assertEqual(ORGANIZATION, json["namespace"])
        self.assertEqual("newrepo", json["name"])


class TestListRepos(ApiTestCase):
    def test_list_app_repos(self):
        self.login(ADMIN_ACCESS_USER)

        # Create an app repo.
        self.postJsonResponse(
            RepositoryList,
            data=dict(
                repository="newrepo", visibility="public", description="", repo_kind="application"
            ),
            expected_code=201,
        )

        json = self.getJsonResponse(
            RepositoryList,
            params=dict(namespace=ADMIN_ACCESS_USER, public=False, repo_kind="application"),
        )

        self.assertEqual(1, len(json["repositories"]))
        self.assertEqual("application", json["repositories"][0]["kind"])

    def test_listrepos_asguest(self):
        # Queries: Base + the list query
        # TODO: Add quota queries
        with patch("features.QUOTA_MANAGEMENT", False):
            with assert_query_count(BASE_QUERY_COUNT + 1):
                json = self.getJsonResponse(RepositoryList, params=dict(public=True))
                self.assertEqual(len(json["repositories"]), 1)

    def assertPublicRepos(self, has_extras=False):
        public_user = model.user.get_user("public")

        # Delete all existing repos under the namespace.
        for repo in list(
            RepositoryTable.select().where(RepositoryTable.namespace_user == public_user)
        ):
            model.gc.purge_repository(repo, force=True)

        # Add public repos until we have enough for a few pages.
        required = set()
        for i in range(0, REPOS_PER_PAGE * 3):
            name = "publicrepo%s" % i
            model.repository.create_repository("public", name, public_user, visibility="public")
            required.add(name)

        # Request results until we no longer have any.
        next_page = None
        while True:
            json = self.getJsonResponse(
                RepositoryList, params=dict(public=True, next_page=next_page)
            )
            for repo in json["repositories"]:
                name = repo["name"]
                if name in required:
                    required.remove(name)
                else:
                    self.assertTrue(has_extras, "Could not find name %s in repos created" % name)

            if "next_page" in json:
                self.assertEqual(len(json["repositories"]), REPOS_PER_PAGE)
            else:
                break

            next_page = json["next_page"]

    def test_listrepos_asguest_withpages(self):
        self.assertPublicRepos()

    def test_listrepos_asorgmember_withpages(self):
        self.login(READ_ACCESS_USER)
        self.assertPublicRepos(has_extras=True)

    def test_listrepos_filter(self):
        self.login(READ_ACCESS_USER)
        json = self.getJsonResponse(
            RepositoryList, params=dict(namespace=ORGANIZATION, public=False)
        )

        self.assertGreater(len(json["repositories"]), 0)

        for repo in json["repositories"]:
            self.assertEqual(ORGANIZATION, repo["namespace"])

    def test_listrepos_allparams(self):
        # Add a repository action count entry for one of the org repos.
        repo = model.repository.get_repository(ORGANIZATION, ORG_REPO)
        RepositoryActionCount.create(repository=repo, count=10, date=datetime.datetime.utcnow())

        self.login(ADMIN_ACCESS_USER)

        # Queries: Base + the list query + the popularity and last modified queries + full perms load
        # TODO: Add quota queries
        with patch("features.QUOTA_MANAGEMENT", False):
            with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 4):
                json = self.getJsonResponse(
                    RepositoryList,
                    params=dict(
                        namespace=ORGANIZATION, public=False, last_modified=True, popularity=True
                    ),
                )

        self.assertGreater(len(json["repositories"]), 0)

        for repository in json["repositories"]:
            self.assertEqual(ORGANIZATION, repository["namespace"])
            if repository["name"] == ORG_REPO:
                self.assertGreater(repository["popularity"], 0)

    def test_listrepos_starred_nouser(self):
        self.getResponse(
            RepositoryList,
            params=dict(last_modified=True, popularity=True, starred=True),
            expected_code=400,
        )

    def test_listrepos_starred(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(
            RepositoryList, params=dict(last_modified=True, popularity=True, starred=True)
        )

        self.assertTrue(len(json["repositories"]) > 0)

        for repo in json["repositories"]:
            self.assertTrue(repo["is_starred"])

    def test_listrepos_asguest_allparams(self):
        json = self.getJsonResponse(
            RepositoryList, params=dict(namespace=ORGANIZATION, public=False, last_modified=True)
        )

        for repo in json["repositories"]:
            self.assertEqual(ORGANIZATION, repo["namespace"])

    def assertRepositoryVisible(self, namespace, name):
        json = self.getJsonResponse(RepositoryList, params=dict(namespace=namespace, public=False))
        self.assertEqual(1, len(json["repositories"]))
        self.assertEqual(name, json["repositories"][0]["name"])

    def assertRepositoryNotVisible(self, namespace, name):
        json = self.getJsonResponse(RepositoryList, params=dict(namespace=namespace, public=False))
        for repo in json["repositories"]:
            self.assertNotEqual(name, repo["name"])

        json = self.getJsonResponse(RepositoryList, params=dict(starred=True))
        for repo in json["repositories"]:
            self.assertNotEqual(name, repo["name"])

    def test_listrepos_starred_filtered(self):
        admin_user = model.user.get_user(ADMIN_ACCESS_USER)
        reader_user = model.user.get_user(READ_ACCESS_USER)

        # Create a new organization.
        new_org = model.organization.create_organization(
            "neworg", "neworg@devtable.com", admin_user
        )
        admin_team = model.team.create_team("admin", new_org, "admin")

        # Add a repository to the organization.
        repo = model.repository.create_repository("neworg", "somerepo", admin_user)

        with self.add_to_team_temporarily(reader_user, admin_team):
            # Star the repository for the user.
            model.repository.star_repository(reader_user, repo)

        # Verify that the user cannot see the repo, since they are no longer allowed to do so.
        self.login(READ_ACCESS_USER)
        self.assertRepositoryNotVisible("neworg", "somerepo")

    @contextmanager
    def add_to_team_temporarily(self, user, team):
        model.team.add_user_to_team(user, team)
        yield
        model.team.remove_user_from_team(
            team.organization.username, team.name, user.username, ADMIN_ACCESS_USER
        )

    def test_listrepos_org_filtered(self):
        admin_user = model.user.get_user(ADMIN_ACCESS_USER)
        reader_user = model.user.get_user(READ_ACCESS_USER)

        # Create a new organization.
        new_org = model.organization.create_organization(
            "neworg", "neworg@devtable.com", admin_user
        )

        admin_team = model.team.create_team("admin", new_org, "admin")
        creator_team = model.team.create_team("creators", new_org, "creator")
        member_team = model.team.create_team("members", new_org, "member")

        # Add a repository to the organization.
        model.repository.create_repository("neworg", "somerepo", admin_user)

        # Verify that the admin user can view it.
        self.login(ADMIN_ACCESS_USER)
        self.assertRepositoryVisible("neworg", "somerepo")

        # Add reader to a creator team under the org and verify they *cannot* see the repository.
        with self.add_to_team_temporarily(reader_user, creator_team):
            self.login(READ_ACCESS_USER)
            self.assertRepositoryNotVisible("neworg", "somerepo")

        # Add reader to a member team under the org and verify they *cannot* see the repository.
        with self.add_to_team_temporarily(reader_user, member_team):
            self.login(READ_ACCESS_USER)
            self.assertRepositoryNotVisible("neworg", "somerepo")

        # Add reader to an admin team under the org and verify they *can* see the repository.
        with self.add_to_team_temporarily(reader_user, admin_team):
            self.login(READ_ACCESS_USER)
            self.assertRepositoryVisible("neworg", "somerepo")

        # Verify that the public user cannot see the repository.
        self.login(PUBLIC_USER)
        self.assertRepositoryNotVisible("neworg", "somerepo")


class TestViewPublicRepository(ApiTestCase):
    def test_normalview(self):
        resp = self.getJsonResponse(Repository, params=dict(repository="public/publicrepo"))
        self.assertFalse("stats" in resp)

    def test_normalview_withstats(self):
        resp = self.getJsonResponse(
            Repository, params=dict(repository="public/publicrepo", includeStats=True)
        )
        self.assertTrue("stats" in resp)

    def test_anon_access_disabled(self):
        import features

        features.ANONYMOUS_ACCESS = False
        try:
            self.getResponse(
                Repository, params=dict(repository="public/publicrepo"), expected_code=401
            )
        finally:
            features.ANONYMOUS_ACCESS = True


class TestUpdateRepo(ApiTestCase):
    SIMPLE_REPO = ADMIN_ACCESS_USER + "/simple"

    def test_updatedescription(self):
        self.login(ADMIN_ACCESS_USER)

        self.putJsonResponse(
            Repository,
            params=dict(repository=self.SIMPLE_REPO),
            data=dict(description="Some cool repo"),
        )

        # Verify the repo description was updated.
        json = self.getJsonResponse(Repository, params=dict(repository=self.SIMPLE_REPO))

        self.assertEqual("Some cool repo", json["description"])


class TestChangeRepoVisibility(ApiTestCase):
    SIMPLE_REPO = ADMIN_ACCESS_USER + "/simple"

    def test_trychangevisibility(self):
        self.login(ADMIN_ACCESS_USER)

        # Make public.
        self.postJsonResponse(
            RepositoryVisibility,
            params=dict(repository=self.SIMPLE_REPO),
            data=dict(visibility="public"),
        )

        # Verify the visibility.
        json = self.getJsonResponse(Repository, params=dict(repository=self.SIMPLE_REPO))

        self.assertEqual(True, json["is_public"])

        # Change the subscription of the namespace.
        self.putJsonResponse(UserPlan, data=dict(plan="personal-2018"))

        # Try to make private.
        self.postJsonResponse(
            RepositoryVisibility,
            params=dict(repository=self.SIMPLE_REPO),
            data=dict(visibility="private"),
            expected_code=402,
        )

        # Verify the visibility.
        json = self.getJsonResponse(Repository, params=dict(repository=self.SIMPLE_REPO))

        self.assertEqual(True, json["is_public"])

    def test_changevisibility(self):
        self.login(ADMIN_ACCESS_USER)

        # Make public.
        self.postJsonResponse(
            RepositoryVisibility,
            params=dict(repository=self.SIMPLE_REPO),
            data=dict(visibility="public"),
        )

        # Verify the visibility.
        json = self.getJsonResponse(Repository, params=dict(repository=self.SIMPLE_REPO))

        self.assertEqual(True, json["is_public"])

        # Make private.
        self.postJsonResponse(
            RepositoryVisibility,
            params=dict(repository=self.SIMPLE_REPO),
            data=dict(visibility="private"),
        )

        # Verify the visibility.
        json = self.getJsonResponse(Repository, params=dict(repository=self.SIMPLE_REPO))

        self.assertEqual(False, json["is_public"])


class TestDeleteRepository(ApiTestCase):
    SIMPLE_REPO = ADMIN_ACCESS_USER + "/simple"
    COMPLEX_REPO = ADMIN_ACCESS_USER + "/complex"

    def test_deleterepo(self):
        self.login(ADMIN_ACCESS_USER)

        # Verify the repo exists.
        self.getResponse(Repository, params=dict(repository=self.SIMPLE_REPO))

        # Add a build queue item for the repo.
        dockerfile_build_queue.put([ADMIN_ACCESS_USER, "simple"], "{}")

        # Delete the repository.
        self.deleteEmptyResponse(Repository, params=dict(repository=self.SIMPLE_REPO))

        # Ensure the queue item is gone.
        self.assertIsNone(dockerfile_build_queue.get())

        # Verify the repo was deleted.
        self.getResponse(Repository, params=dict(repository=self.SIMPLE_REPO), expected_code=404)

    def test_verify_queue_removal(self):
        self.login(ADMIN_ACCESS_USER)

        # Verify the repo exists.
        self.getResponse(Repository, params=dict(repository=self.SIMPLE_REPO))

        # Add a build queue item for the repo and another repo.
        dockerfile_build_queue.put([ADMIN_ACCESS_USER, "simple"], "{}", available_after=-1)
        dockerfile_build_queue.put([ADMIN_ACCESS_USER, "anotherrepo"], "{}", available_after=-1)

        # Delete the repository.
        self.deleteEmptyResponse(Repository, params=dict(repository=self.SIMPLE_REPO))

        # Ensure the other queue item is still present.
        self.assertIsNotNone(dockerfile_build_queue.get())

    def test_deleterepo2(self):
        self.login(ADMIN_ACCESS_USER)

        # Verify the repo exists.
        self.getResponse(Repository, params=dict(repository=self.COMPLEX_REPO))

        self.deleteEmptyResponse(Repository, params=dict(repository=self.COMPLEX_REPO))

        # Verify the repo was deleted.
        self.getResponse(Repository, params=dict(repository=self.COMPLEX_REPO), expected_code=404)

    def test_populate_and_delete_repo(self):
        self.login(ADMIN_ACCESS_USER)

        # Verify the repo exists.
        self.getResponse(Repository, params=dict(repository=self.COMPLEX_REPO))

        # Make sure the repository has some images and tags.
        repo_ref = registry_model.lookup_repository(ADMIN_ACCESS_USER, "complex")
        self.assertTrue(len(list(registry_model.list_all_active_repository_tags(repo_ref))) > 0)

        # Add some data for the repository, in addition to is already existing images and tags.
        repository = model.repository.get_repository(ADMIN_ACCESS_USER, "complex")

        # Create some access tokens.
        access_token = model.token.create_access_token(repository, "read")
        model.token.create_access_token(repository, "write")

        delegate_token = model.token.create_delegate_token(
            ADMIN_ACCESS_USER, "complex", "sometoken", "read"
        )
        model.token.create_delegate_token(ADMIN_ACCESS_USER, "complex", "sometoken", "write")

        # Create some repository builds.
        model.build.create_repository_build(repository, access_token, {}, "someid", "foobar")
        model.build.create_repository_build(repository, delegate_token, {}, "someid2", "foobar2")

        # Create some notifications.
        model.notification.create_repo_notification(repository, "repo_push", "hipchat", {}, {})
        model.notification.create_repo_notification(repository, "build_queued", "slack", {}, {})

        # Create some logs.
        logs_model.log_action("push_repo", ADMIN_ACCESS_USER, repository=repository)
        logs_model.log_action("push_repo", ADMIN_ACCESS_USER, repository=repository)

        # Create some build triggers.
        user = model.user.get_user(ADMIN_ACCESS_USER)
        model.build.create_build_trigger(repository, "github", "sometoken", user)
        model.build.create_build_trigger(repository, "github", "anothertoken", user)

        # Create some email authorizations.
        model.repository.create_email_authorization_for_repo(
            ADMIN_ACCESS_USER, "complex", "a@b.com"
        )
        model.repository.create_email_authorization_for_repo(
            ADMIN_ACCESS_USER, "complex", "b@c.com"
        )

        # Create some repository action count entries.
        RepositoryActionCount.create(repository=repository, date=datetime.datetime.now(), count=1)
        RepositoryActionCount.create(
            repository=repository,
            date=datetime.datetime.now() - datetime.timedelta(days=2),
            count=2,
        )
        RepositoryActionCount.create(
            repository=repository,
            date=datetime.datetime.now() - datetime.timedelta(days=5),
            count=6,
        )

        repo_ref = registry_model.lookup_repository(ADMIN_ACCESS_USER, "complex")
        tag = registry_model.get_repo_tag(repo_ref, "prod")
        manifest = registry_model.get_manifest_for_tag(tag)

        # Create some labels.
        registry_model.create_manifest_label(manifest, "foo", "bar", "manifest")
        registry_model.create_manifest_label(manifest, "foo", "baz", "manifest")
        registry_model.create_manifest_label(
            manifest, "something", "{}", "api", media_type_name="application/json"
        )

        registry_model.create_manifest_label(manifest, "something", '{"some": "json"}', "manifest")

        # Delete the repository.
        with check_transitive_modifications():
            self.deleteEmptyResponse(Repository, params=dict(repository=self.COMPLEX_REPO))

        # Verify the repo was deleted.
        self.getResponse(Repository, params=dict(repository=self.COMPLEX_REPO), expected_code=404)


class TestGetRepository(ApiTestCase):
    PUBLIC_REPO = PUBLIC_USER + "/publicrepo"

    def test_get_largerepo(self):
        self.login(ADMIN_ACCESS_USER)

        # base + repo + is_starred + tags
        with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 3):
            self.getJsonResponse(Repository, params=dict(repository=ADMIN_ACCESS_USER + "/simple"))

        # base + repo + is_starred + tags
        with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 3):
            json = self.getJsonResponse(
                Repository, params=dict(repository=ADMIN_ACCESS_USER + "/gargantuan")
            )

        self.assertEqual(ADMIN_ACCESS_USER, json["namespace"])
        self.assertEqual("gargantuan", json["name"])

        self.assertEqual(False, json["is_public"])

    def test_getrepo_badnames(self):
        self.login(ADMIN_ACCESS_USER)

        bad_names = ["logs", "build", "tokens", "foo.bar", "foo-bar", "foo_bar"]

        # For each bad name, create the repo.
        for bad_name in bad_names:
            json = self.postJsonResponse(
                RepositoryList,
                expected_code=201,
                data=dict(repository=bad_name, visibility="public", description=""),
            )

            # Make sure we can retrieve its information.
            json = self.getJsonResponse(
                Repository, params=dict(repository=ADMIN_ACCESS_USER + "/" + bad_name)
            )

            self.assertEqual(ADMIN_ACCESS_USER, json["namespace"])
            self.assertEqual(bad_name, json["name"])
            self.assertEqual(True, json["is_public"])

    def test_getrepo_public_asguest(self):
        json = self.getJsonResponse(Repository, params=dict(repository=self.PUBLIC_REPO))

        self.assertEqual(PUBLIC_USER, json["namespace"])
        self.assertEqual("publicrepo", json["name"])

        self.assertEqual(True, json["is_public"])
        self.assertEqual(False, json["is_organization"])

        self.assertEqual(False, json["can_write"])
        self.assertEqual(False, json["can_admin"])

        assert "latest" in json["tags"]

    def test_getrepo_public_asowner(self):
        self.login(PUBLIC_USER)

        json = self.getJsonResponse(Repository, params=dict(repository=self.PUBLIC_REPO))

        self.assertEqual(False, json["is_organization"])
        self.assertEqual(True, json["can_write"])
        self.assertEqual(True, json["can_admin"])

    def test_getrepo_building(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(
            Repository, params=dict(repository=ADMIN_ACCESS_USER + "/building")
        )

        self.assertEqual(True, json["can_write"])
        self.assertEqual(True, json["can_admin"])
        self.assertEqual(False, json["is_organization"])

    def test_getrepo_org_asnonmember(self):
        self.getResponse(
            Repository, params=dict(repository=ORGANIZATION + "/" + ORG_REPO), expected_code=401
        )

    def test_getrepo_org_asreader(self):
        self.login(READ_ACCESS_USER)

        json = self.getJsonResponse(
            Repository, params=dict(repository=ORGANIZATION + "/" + ORG_REPO)
        )

        self.assertEqual(ORGANIZATION, json["namespace"])
        self.assertEqual(ORG_REPO, json["name"])

        self.assertEqual(False, json["can_write"])
        self.assertEqual(False, json["can_admin"])

        self.assertEqual(True, json["is_organization"])

    def test_getrepo_org_asadmin(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(
            Repository, params=dict(repository=ORGANIZATION + "/" + ORG_REPO)
        )

        self.assertEqual(True, json["can_write"])
        self.assertEqual(True, json["can_admin"])

        self.assertEqual(True, json["is_organization"])


class TestRepositoryBuildResource(ApiTestCase):
    def test_repo_build_invalid_url(self):
        self.login(ADMIN_ACCESS_USER)

        self.postJsonResponse(
            RepositoryBuildList,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple"),
            data=dict(archive_url="hppt://quay.io"),
            expected_code=400,
        )

    def test_cancel_invalidbuild(self):
        self.login(ADMIN_ACCESS_USER)

        self.deleteResponse(
            RepositoryBuildResource,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", build_uuid="invalid"),
            expected_code=404,
        )

    def test_cancel_waitingbuild(self):
        self.login(ADMIN_ACCESS_USER)

        # Request a (fake) build.
        json = self.postJsonResponse(
            RepositoryBuildList,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple"),
            data=dict(file_id="foobarbaz"),
            expected_code=201,
        )

        uuid = json["id"]

        # Check for the build.
        json = self.getJsonResponse(
            RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + "/simple")
        )

        self.assertEqual(1, len(json["builds"]))
        self.assertEqual(uuid, json["builds"][0]["id"])

        # Find the build's queue item.
        build_ref = database.RepositoryBuild.get(uuid=uuid)
        queue_item = database.QueueItem.get(id=build_ref.queue_id)

        self.assertTrue(queue_item.available)
        self.assertTrue(queue_item.retries_remaining > 0)

        # Cancel the build.
        self.deleteResponse(
            RepositoryBuildResource,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", build_uuid=uuid),
            expected_code=201,
        )

        # Check for the build.
        json = self.getJsonResponse(
            RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + "/simple")
        )

        self.assertEqual(1, len(json["builds"]))
        self.assertEqual("cancelled", json["builds"][0]["phase"])

        # Check for the build's queue item.
        try:
            database.QueueItem.get(id=build_ref.queue_id)
            self.fail("QueueItem still exists for build")
        except database.QueueItem.DoesNotExist:
            pass

    def test_attemptcancel_scheduledbuild(self):
        self.login(ADMIN_ACCESS_USER)

        # Request a (fake) build.
        json = self.postJsonResponse(
            RepositoryBuildList,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple"),
            data=dict(file_id="foobarbaz"),
            expected_code=201,
        )

        uuid = json["id"]

        # Check for the build.
        json = self.getJsonResponse(
            RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + "/simple")
        )

        self.assertEqual(1, len(json["builds"]))
        self.assertEqual(uuid, json["builds"][0]["id"])

        # Set queue item to be picked up.
        build_ref = database.RepositoryBuild.get(uuid=uuid)
        qi = database.QueueItem.get(id=build_ref.queue_id)
        qi.available = False
        qi.save()

        # Try to cancel the build.
        self.deleteResponse(
            RepositoryBuildResource,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", build_uuid=uuid),
            expected_code=201,
        )

    def test_attemptcancel_workingbuild(self):
        self.login(ADMIN_ACCESS_USER)

        # Request a (fake) build.
        json = self.postJsonResponse(
            RepositoryBuildList,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple"),
            data=dict(file_id="foobarbaz"),
            expected_code=201,
        )

        uuid = json["id"]

        # Check for the build.
        json = self.getJsonResponse(
            RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + "/simple")
        )

        self.assertEqual(1, len(json["builds"]))
        self.assertEqual(uuid, json["builds"][0]["id"])

        # Set the build to a different phase.
        rb = database.RepositoryBuild.get(uuid=uuid)
        rb.phase = database.BUILD_PHASE.BUILDING
        rb.save()

        # Try to cancel the build.
        self.deleteResponse(
            RepositoryBuildResource,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", build_uuid=uuid),
            expected_code=400,
        )


class TestRepoBuilds(ApiTestCase):
    def test_getrepo_nobuilds(self):
        self.login(ADMIN_ACCESS_USER)

        # Queries: Permission + the list query + app check
        with assert_query_count(2):
            json = self.getJsonResponse(
                RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + "/simple")
            )

        assert len(json["builds"]) == 0

    def test_getrepobuilds(self):
        self.login(ADMIN_ACCESS_USER)

        # Queries: Permission + the list query + app check
        with assert_query_count(2):
            json = self.getJsonResponse(
                RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + "/building")
            )

        assert len(json["builds"]) > 0
        build = json["builds"][-1]

        assert "id" in build
        assert "status" in build

        # Check the status endpoint.
        status_json = self.getJsonResponse(
            RepositoryBuildStatus,
            params=dict(repository=ADMIN_ACCESS_USER + "/building", build_uuid=build["id"]),
        )

        self.assertEqual(status_json["id"], build["id"])
        self.assertEqual(status_json["resource_key"], build["resource_key"])
        self.assertEqual(status_json["trigger"], build["trigger"])


class TestRequestRepoBuild(ApiTestCase):
    def test_requestbuild_noidurl(self):
        self.login(ADMIN_ACCESS_USER)

        # Request a (fake) build without a file ID or URL.
        self.postResponse(
            RepositoryBuildList,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple"),
            data=dict(),
            expected_code=400,
        )

    def test_requestbuild_invalidurls(self):
        self.login(ADMIN_ACCESS_USER)

        # Request a (fake) build with and invalid URL.
        self.postResponse(
            RepositoryBuildList,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple"),
            data=dict(archive_url="foobarbaz"),
            expected_code=400,
        )

        self.postResponse(
            RepositoryBuildList,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple"),
            data=dict(archive_url="file://foobarbaz"),
            expected_code=400,
        )

    def test_requestrepobuild_withurl(self):
        self.login(ADMIN_ACCESS_USER)

        # Ensure we are not yet building.
        json = self.getJsonResponse(
            RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + "/simple")
        )

        assert len(json["builds"]) == 0

        # Request a (fake) build.
        self.postResponse(
            RepositoryBuildList,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple"),
            data=dict(archive_url="http://quay.io/robots.txt"),
            expected_code=201,
        )

        # Check for the build.
        json = self.getJsonResponse(
            RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + "/simple")
        )

        assert len(json["builds"]) > 0
        self.assertEqual("http://quay.io/robots.txt", json["builds"][0]["archive_url"])

    def test_requestrepobuild_withfile(self):
        self.login(ADMIN_ACCESS_USER)

        # Ensure we are not yet building.
        json = self.getJsonResponse(
            RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + "/simple")
        )

        assert len(json["builds"]) == 0

        # Request a (fake) build.
        self.postResponse(
            RepositoryBuildList,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple"),
            data=dict(file_id="foobarbaz"),
            expected_code=201,
        )

        # Check for the build.
        json = self.getJsonResponse(
            RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + "/simple")
        )

        assert len(json["builds"]) > 0

    def test_requestrepobuild_with_robot(self):
        self.login(ADMIN_ACCESS_USER)

        # Ensure we are not yet building.
        json = self.getJsonResponse(
            RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + "/simple")
        )

        assert len(json["builds"]) == 0

        # Request a (fake) build.
        pull_robot = ADMIN_ACCESS_USER + "+dtrobot"
        self.postResponse(
            RepositoryBuildList,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple"),
            data=dict(file_id="foobarbaz", pull_robot=pull_robot),
            expected_code=201,
        )

        # Check for the build.
        json = self.getJsonResponse(
            RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + "/building")
        )

        assert len(json["builds"]) > 0

    def test_requestrepobuild_with_invalid_robot(self):
        self.login(ADMIN_ACCESS_USER)

        # Request a (fake) build.
        pull_robot = ADMIN_ACCESS_USER + "+invalidrobot"
        self.postResponse(
            RepositoryBuildList,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple"),
            data=dict(file_id="foobarbaz", pull_robot=pull_robot),
            expected_code=404,
        )

    def test_requestrepobuild_with_unauthorized_robot(self):
        self.login(ADMIN_ACCESS_USER)

        # Request a (fake) build.
        pull_robot = "freshuser+anotherrobot"
        self.postResponse(
            RepositoryBuildList,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple"),
            data=dict(file_id="foobarbaz", pull_robot=pull_robot),
            expected_code=403,
        )


class TestRepositoryEmail(ApiTestCase):
    def test_emailnotauthorized(self):
        self.login(ADMIN_ACCESS_USER)

        # Verify the e-mail address is not authorized.
        self.getResponse(
            RepositoryAuthorizedEmail,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", email="test@example.com"),
            expected_code=404,
        )

    def test_emailnotauthorized_butsent(self):
        self.login(ADMIN_ACCESS_USER)

        # Verify the e-mail address is not authorized.
        json = self.getJsonResponse(
            RepositoryAuthorizedEmail,
            params=dict(
                repository=ADMIN_ACCESS_USER + "/simple", email="jschorr+other@devtable.com"
            ),
        )

        self.assertEqual(False, json["confirmed"])
        self.assertEqual(ADMIN_ACCESS_USER, json["namespace"])
        self.assertEqual("simple", json["repository"])

    def test_emailauthorized(self):
        self.login(ADMIN_ACCESS_USER)

        # Verify the e-mail address is authorized.
        json = self.getJsonResponse(
            RepositoryAuthorizedEmail,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", email="jschorr@devtable.com"),
        )

        self.assertEqual(True, json["confirmed"])
        self.assertEqual(ADMIN_ACCESS_USER, json["namespace"])
        self.assertEqual("simple", json["repository"])

    def test_send_email_authorization(self):
        self.login(ADMIN_ACCESS_USER)

        # Send the email.
        json = self.postJsonResponse(
            RepositoryAuthorizedEmail,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", email="jschorr+foo@devtable.com"),
        )

        self.assertEqual(False, json["confirmed"])
        self.assertEqual(ADMIN_ACCESS_USER, json["namespace"])
        self.assertEqual("simple", json["repository"])


class TestRepositoryNotifications(ApiTestCase):
    def test_testnotification(self):
        self.login(ADMIN_ACCESS_USER)

        # Add a notification.
        json = self.postJsonResponse(
            RepositoryNotificationList,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple"),
            data=dict(
                config={"url": "http://example.com"},
                event="repo_push",
                method="webhook",
                eventConfig={},
            ),
            expected_code=201,
        )
        uuid = json["uuid"]

        self.assertIsNone(notification_queue.get())

        # Issue a test notification.
        self.postJsonResponse(
            TestRepositoryNotification,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", uuid=uuid),
        )

        # Ensure the item is in the queue.
        time.sleep(1)  # Makes sure the queue get works on MySQL with its second-level precision.
        found = notification_queue.get()
        self.assertIsNotNone(found)
        self.assertTrue("notification_uuid" in found["body"])

    def test_webhooks(self):
        self.login(ADMIN_ACCESS_USER)

        # Add a notification.
        json = self.postJsonResponse(
            RepositoryNotificationList,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple"),
            data=dict(
                config={"url": "http://example.com"},
                event="repo_push",
                method="webhook",
                eventConfig={},
            ),
            expected_code=201,
        )

        self.assertEqual("repo_push", json["event"])
        self.assertEqual("webhook", json["method"])
        self.assertEqual("http://example.com", json["config"]["url"])
        self.assertIsNone(json["title"])

        wid = json["uuid"]

        # Get the notification.
        json = self.getJsonResponse(
            RepositoryNotification, params=dict(repository=ADMIN_ACCESS_USER + "/simple", uuid=wid)
        )

        self.assertEqual(wid, json["uuid"])
        self.assertEqual("repo_push", json["event"])
        self.assertEqual("webhook", json["method"])
        self.assertIsNone(json["title"])

        # Verify the notification is listed.
        json = self.getJsonResponse(
            RepositoryNotificationList, params=dict(repository=ADMIN_ACCESS_USER + "/simple")
        )

        ids = [w["uuid"] for w in json["notifications"]]
        assert wid in ids

        # Delete the notification.
        self.deleteEmptyResponse(
            RepositoryNotification,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", uuid=wid),
            expected_code=204,
        )

        # Verify the notification is gone.
        self.getResponse(
            RepositoryNotification,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", uuid=wid),
            expected_code=404,
        )

        # Add another notification.
        json = self.postJsonResponse(
            RepositoryNotificationList,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple"),
            data=dict(
                config={"url": "http://example.com"},
                event="repo_push",
                method="webhook",
                title="Some Notification",
                eventConfig={},
            ),
            expected_code=201,
        )

        self.assertEqual("repo_push", json["event"])
        self.assertEqual("webhook", json["method"])
        self.assertEqual("http://example.com", json["config"]["url"])
        self.assertEqual("Some Notification", json["title"])

        wid = json["uuid"]

        # Get the notification.
        json = self.getJsonResponse(
            RepositoryNotification, params=dict(repository=ADMIN_ACCESS_USER + "/simple", uuid=wid)
        )

        self.assertEqual(wid, json["uuid"])
        self.assertEqual("repo_push", json["event"])
        self.assertEqual("webhook", json["method"])
        self.assertEqual("Some Notification", json["title"])


class TestRestoreTag(ApiTestCase):
    def test_restoretag_invalidtag(self):
        self.login(ADMIN_ACCESS_USER)

        self.postResponse(
            RestoreTag,
            params=dict(repository=ADMIN_ACCESS_USER + "/history", tag="invalidtag"),
            data=dict(manifest_digest="invalid_image"),
            expected_code=404,
        )

    def test_restoretag_invalidimage(self):
        self.login(ADMIN_ACCESS_USER)

        self.postResponse(
            RestoreTag,
            params=dict(repository=ADMIN_ACCESS_USER + "/history", tag="latest"),
            data=dict(manifest_digest="invalid_image"),
            expected_code=404,
        )

    def test_restoretag_invalidmanifest(self):
        self.login(ADMIN_ACCESS_USER)

        self.postResponse(
            RestoreTag,
            params=dict(repository=ADMIN_ACCESS_USER + "/history", tag="latest"),
            data=dict(manifest_digest="invalid_digest"),
            expected_code=404,
        )

    def test_restoretag(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(
            ListRepositoryTags, params=dict(repository=ADMIN_ACCESS_USER + "/history", tag="latest")
        )

        self.assertEqual(2, len(json["tags"]))
        self.assertFalse("end_ts" in json["tags"][0])

        previous_image_id = json["tags"][1]["manifest_digest"]

        self.postJsonResponse(
            RestoreTag,
            params=dict(repository=ADMIN_ACCESS_USER + "/history", tag="latest"),
            data=dict(manifest_digest=previous_image_id),
        )

        json = self.getJsonResponse(
            ListRepositoryTags, params=dict(repository=ADMIN_ACCESS_USER + "/history", tag="latest")
        )
        self.assertEqual(3, len(json["tags"]))
        self.assertFalse("end_ts" in json["tags"][0])
        self.assertEqual(previous_image_id, json["tags"][0]["manifest_digest"])

    def test_restoretag_to_digest(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(
            ListRepositoryTags, params=dict(repository=ADMIN_ACCESS_USER + "/history", tag="latest")
        )

        self.assertEqual(2, len(json["tags"]))
        self.assertFalse("end_ts" in json["tags"][0])

        previous_manifest = json["tags"][1]["manifest_digest"]

        self.postJsonResponse(
            RestoreTag,
            params=dict(repository=ADMIN_ACCESS_USER + "/history", tag="latest"),
            data=dict(manifest_digest=previous_manifest),
        )

        json = self.getJsonResponse(
            ListRepositoryTags, params=dict(repository=ADMIN_ACCESS_USER + "/history", tag="latest")
        )
        self.assertEqual(3, len(json["tags"]))
        self.assertFalse("end_ts" in json["tags"][0])
        self.assertEqual(previous_manifest, json["tags"][0]["manifest_digest"])


class TestListAndDeleteTag(ApiTestCase):
    def test_invalid_tags(self):
        self.login(ADMIN_ACCESS_USER)

        # List the images for staging.
        json = self.getJsonResponse(
            ListRepositoryTags,
            params=dict(
                repository=ADMIN_ACCESS_USER + "/complex",
                specificTag="staging",
                onlyActiveTags=True,
            ),
        )

        staging_images = json["tags"]

        # Try to add some invalid tags.
        self.putResponse(
            RepositoryTag,
            params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="-fail"),
            data=dict(manifest_digest=staging_images[0]["manifest_digest"]),
            expected_code=400,
        )

        self.putResponse(
            RepositoryTag,
            params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="北京"),
            data=dict(manifest_digest=staging_images[0]["manifest_digest"]),
            expected_code=400,
        )

    def test_listdeletecreateandmovetag(self):
        self.login(ADMIN_ACCESS_USER)

        # List the images for prod.
        json = self.getJsonResponse(
            ListRepositoryTags,
            params=dict(
                repository=ADMIN_ACCESS_USER + "/complex", specificTag="prod", onlyActiveTags=True
            ),
        )

        prod_images = json["tags"]
        assert len(prod_images) > 0

        # List the images for staging.
        json = self.getJsonResponse(
            ListRepositoryTags,
            params=dict(
                repository=ADMIN_ACCESS_USER + "/complex",
                specificTag="staging",
                onlyActiveTags=True,
            ),
        )

        staging_images = json["tags"]
        assert len(prod_images) == len(staging_images)

        # Delete prod.
        self.deleteEmptyResponse(
            RepositoryTag,
            params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="prod"),
            expected_code=204,
        )

        # Make sure the tag is gone.
        json = self.getJsonResponse(
            ListRepositoryTags,
            params=dict(
                repository=ADMIN_ACCESS_USER + "/complex", specificTag="prod", onlyActiveTags=True
            ),
            expected_code=200,
        )
        assert len(json["tags"]) == 0

        # Make the sure the staging images are still there.
        json = self.getJsonResponse(
            ListRepositoryTags,
            params=dict(
                repository=ADMIN_ACCESS_USER + "/complex",
                specificTag="staging",
                onlyActiveTags=True,
            ),
        )

        # Require a valid tag name.
        self.putResponse(
            RepositoryTag,
            params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="-fail"),
            data=dict(manifest_digest=json["tags"][0]["manifest_digest"]),
            expected_code=400,
        )

        # Add a new tag to the staging image.
        self.putResponse(
            RepositoryTag,
            params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="sometag"),
            data=dict(manifest_digest=json["tags"][0]["manifest_digest"]),
            expected_code=201,
        )

        # Make sure the tag is present.
        json = self.getJsonResponse(
            ListRepositoryTags,
            params=dict(
                repository=ADMIN_ACCESS_USER + "/complex",
                specificTag="sometag",
                onlyActiveTags=True,
            ),
        )

        assert len(json["tags"]) > 0

        # Move the tag.
        self.putResponse(
            RepositoryTag,
            params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="sometag"),
            data=dict(manifest_digest=json["tags"][0]["manifest_digest"]),
            expected_code=201,
        )

        # Make sure the tag has moved.
        json = self.getJsonResponse(
            ListRepositoryTags,
            params=dict(
                repository=ADMIN_ACCESS_USER + "/complex",
                specificTag="sometag",
                onlyActiveTags=True,
            ),
        )

        sometag_new_images = json["tags"]
        assert len(sometag_new_images) > 0

    def test_deletesubtag(self):
        self.login(ADMIN_ACCESS_USER)

        # List the images for prod.
        json = self.getJsonResponse(
            ListRepositoryTags,
            params=dict(
                repository=ADMIN_ACCESS_USER + "/complex", specificTag="prod", onlyActiveTags=True
            ),
        )

        prod_images = json["tags"]
        assert len(prod_images) > 0

        # Delete staging.
        self.deleteEmptyResponse(
            RepositoryTag,
            params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="staging"),
            expected_code=204,
        )

        # Make sure the prod images are still around.
        json = self.getJsonResponse(
            ListRepositoryTags,
            params=dict(
                repository=ADMIN_ACCESS_USER + "/complex", specificTag="prod", onlyActiveTags=True
            ),
        )

        self.assertEqual(prod_images, json["tags"])

    def test_listtag_digest(self):
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(
            ListRepositoryTags,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", page=1, limit=1),
        )
        self.assertTrue("manifest_digest" in json["tags"][0])

    def test_listtagpagination(self):
        self.login(ADMIN_ACCESS_USER)

        repo_ref = registry_model.lookup_repository(ADMIN_ACCESS_USER, "simple")
        latest_tag = registry_model.get_repo_tag(repo_ref, "latest")

        # Create 8 tags in the simple repo.
        remaining_tags = {"latest", "prod"}
        for i in range(1, 9):
            tag_name = "tag" + str(i)
            remaining_tags.add(tag_name)
            assert registry_model.retarget_tag(
                repo_ref, tag_name, latest_tag.manifest, storage, docker_v2_signing_key
            )

        # Make sure we can iterate over all of them.
        json = self.getJsonResponse(
            ListRepositoryTags,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", page=1, limit=5),
        )
        self.assertEqual(1, json["page"])
        self.assertEqual(5, len(json["tags"]))
        self.assertTrue(json["has_additional"])

        names = {tag["name"] for tag in json["tags"]}
        remaining_tags = remaining_tags - names
        self.assertEqual(5, len(remaining_tags))

        json = self.getJsonResponse(
            ListRepositoryTags,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", page=2, limit=5),
        )

        self.assertEqual(2, json["page"])
        self.assertEqual(5, len(json["tags"]))
        self.assertFalse(json["has_additional"])

        names = {tag["name"] for tag in json["tags"]}
        remaining_tags = remaining_tags - names
        self.assertEqual(0, len(remaining_tags))

        json = self.getJsonResponse(
            ListRepositoryTags,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", page=3, limit=5),
        )

        self.assertEqual(3, json["page"])
        self.assertEqual(0, len(json["tags"]))
        self.assertFalse(json["has_additional"])


class TestRepoPermissions(ApiTestCase):
    def listUserPermissions(self, namespace=ADMIN_ACCESS_USER, repo="simple"):
        return self.getJsonResponse(
            RepositoryUserPermissionList, params=dict(repository=namespace + "/" + repo)
        )["permissions"]

    def listTeamPermissions(self):
        response = self.getJsonResponse(
            RepositoryTeamPermissionList, params=dict(repository=ORGANIZATION + "/" + ORG_REPO)
        )
        return response["permissions"]

    def test_userpermissions_underorg(self):
        self.login(ADMIN_ACCESS_USER)

        permissions = self.listUserPermissions(namespace=ORGANIZATION, repo=ORG_REPO)

        self.assertEqual(1, len(permissions))
        assert "outsideorg" in permissions
        self.assertEqual("read", permissions["outsideorg"]["role"])
        self.assertEqual(False, permissions["outsideorg"]["is_org_member"])

        # Add another user.
        self.putJsonResponse(
            RepositoryUserPermission,
            params=dict(repository=ORGANIZATION + "/" + ORG_REPO, username=ADMIN_ACCESS_USER),
            data=dict(role="admin"),
        )

        # Verify the user is present.
        permissions = self.listUserPermissions(namespace=ORGANIZATION, repo=ORG_REPO)

        self.assertEqual(2, len(permissions))
        assert ADMIN_ACCESS_USER in permissions
        self.assertEqual("admin", permissions[ADMIN_ACCESS_USER]["role"])
        self.assertEqual(True, permissions[ADMIN_ACCESS_USER]["is_org_member"])

    def test_userpermissions(self):
        self.login(ADMIN_ACCESS_USER)

        # The repo should start with just the admin as a user perm.
        permissions = self.listUserPermissions()

        self.assertEqual(1, len(permissions))
        assert ADMIN_ACCESS_USER in permissions
        self.assertEqual("admin", permissions[ADMIN_ACCESS_USER]["role"])
        self.assertFalse("is_org_member" in permissions[ADMIN_ACCESS_USER])

        # Add another user.
        self.putJsonResponse(
            RepositoryUserPermission,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", username=NO_ACCESS_USER),
            data=dict(role="read"),
        )

        # Verify the user is present.
        permissions = self.listUserPermissions()

        self.assertEqual(2, len(permissions))
        assert NO_ACCESS_USER in permissions
        self.assertEqual("read", permissions[NO_ACCESS_USER]["role"])
        self.assertFalse("is_org_member" in permissions[NO_ACCESS_USER])

        json = self.getJsonResponse(
            RepositoryUserPermission,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", username=NO_ACCESS_USER),
        )
        self.assertEqual("read", json["role"])

        # Change the user's permissions.
        self.putJsonResponse(
            RepositoryUserPermission,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", username=NO_ACCESS_USER),
            data=dict(role="admin"),
        )

        # Verify.
        permissions = self.listUserPermissions()

        self.assertEqual(2, len(permissions))
        assert NO_ACCESS_USER in permissions
        self.assertEqual("admin", permissions[NO_ACCESS_USER]["role"])

        # Delete the user's permission.
        self.deleteEmptyResponse(
            RepositoryUserPermission,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", username=NO_ACCESS_USER),
        )

        # Verify.
        permissions = self.listUserPermissions()

        self.assertEqual(1, len(permissions))
        assert not NO_ACCESS_USER in permissions

    def test_teampermissions(self):
        self.login(ADMIN_ACCESS_USER)

        # The repo should start with just the readers as a team perm.
        permissions = self.listTeamPermissions()

        self.assertEqual(1, len(permissions))
        assert "readers" in permissions
        self.assertEqual("read", permissions["readers"]["role"])

        # Add another team.
        self.putJsonResponse(
            RepositoryTeamPermission,
            params=dict(repository=ORGANIZATION + "/" + ORG_REPO, teamname="owners"),
            data=dict(role="write"),
        )

        # Verify the team is present.
        permissions = self.listTeamPermissions()

        self.assertEqual(2, len(permissions))
        assert "owners" in permissions
        self.assertEqual("write", permissions["owners"]["role"])

        json = self.getJsonResponse(
            RepositoryTeamPermission,
            params=dict(repository=ORGANIZATION + "/" + ORG_REPO, teamname="owners"),
        )
        self.assertEqual("write", json["role"])

        # Change the team's permissions.
        self.putJsonResponse(
            RepositoryTeamPermission,
            params=dict(repository=ORGANIZATION + "/" + ORG_REPO, teamname="owners"),
            data=dict(role="admin"),
        )

        # Verify.
        permissions = self.listTeamPermissions()

        self.assertEqual(2, len(permissions))
        assert "owners" in permissions
        self.assertEqual("admin", permissions["owners"]["role"])

        # Delete the team's permission.
        self.deleteEmptyResponse(
            RepositoryTeamPermission,
            params=dict(repository=ORGANIZATION + "/" + ORG_REPO, teamname="owners"),
        )

        # Verify.
        permissions = self.listTeamPermissions()

        self.assertEqual(1, len(permissions))
        assert not "owners" in permissions


class TestApiTokens(ApiTestCase):
    def listTokens(self):
        return self.getJsonResponse(
            RepositoryTokenList, params=dict(repository=ADMIN_ACCESS_USER + "/simple")
        )["tokens"]


class TestUserCard(ApiTestCase):
    def test_getusercard(self):
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(UserCard)

        self.assertEqual("4242", json["card"]["last4"])
        self.assertEqual("Visa", json["card"]["type"])

    def test_setusercard_error(self):
        self.login(ADMIN_ACCESS_USER)
        # token not a valid param anymore. This becomes a checkout session instead
        json = self.postJsonResponse(UserCard, data=dict(token="sometoken"), expected_code=400)


class TestOrgCard(ApiTestCase):
    def test_getorgcard(self):
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(OrganizationCard, params=dict(orgname=ORGANIZATION))

        self.assertEqual("4242", json["card"]["last4"])
        self.assertEqual("Visa", json["card"]["type"])


class TestUserSubscription(ApiTestCase):
    def getSubscription(self):
        return self.getJsonResponse(UserPlan)

    def test_updateplan(self):
        self.login(ADMIN_ACCESS_USER)

        # Change the plan.
        self.putJsonResponse(UserPlan, data=dict(plan="free"))

        # Verify
        sub = self.getSubscription()
        self.assertEqual("free", sub["plan"])

        # Change the plan.
        self.putJsonResponse(UserPlan, data=dict(plan="bus-large-2018"))

        # Verify
        sub = self.getSubscription()
        self.assertEqual("bus-large-2018", sub["plan"])


class TestOrgSubscription(ApiTestCase):
    def getSubscription(self):
        return self.getJsonResponse(OrganizationPlan, params=dict(orgname=ORGANIZATION))

    def test_updateplan(self):
        self.login(ADMIN_ACCESS_USER)

        # Change the plan.
        self.putJsonResponse(
            OrganizationPlan, params=dict(orgname=ORGANIZATION), data=dict(plan="free")
        )

        # Verify
        sub = self.getSubscription()
        self.assertEqual("free", sub["plan"])

        # Change the plan.
        self.putJsonResponse(
            OrganizationPlan, params=dict(orgname=ORGANIZATION), data=dict(plan="bus-large-2018")
        )

        # Verify
        sub = self.getSubscription()
        self.assertEqual("bus-large-2018", sub["plan"])


class TestUserRobots(ApiTestCase):
    def getRobotNames(self):
        return [r["name"] for r in self.getJsonResponse(UserRobotList)["robots"]]

    def test_robot_list(self):
        self.login(NO_ACCESS_USER)

        # Create some robots.
        self.putJsonResponse(UserRobot, params=dict(robot_shortname="bender"), expected_code=201)

        self.putJsonResponse(UserRobot, params=dict(robot_shortname="goldy"), expected_code=201)

        self.putJsonResponse(UserRobot, params=dict(robot_shortname="coolbot"), expected_code=201)

        # Queries: Base + the lookup query
        with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT):
            self.getJsonResponse(UserRobotList)

        # Queries: Base + the lookup query
        with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT):
            self.getJsonResponse(UserRobotList, params=dict(permissions=True))

    def test_robots(self):
        self.login(NO_ACCESS_USER)

        # Create a robot.
        json = self.putJsonResponse(
            UserRobot, params=dict(robot_shortname="bender"), expected_code=201
        )

        self.assertEqual(NO_ACCESS_USER + "+bender", json["name"])

        # Verify.
        robots = self.getRobotNames()
        assert NO_ACCESS_USER + "+bender" in robots

        # Delete the robot.
        self.deleteEmptyResponse(UserRobot, params=dict(robot_shortname="bender"))

        # Verify.
        robots = self.getRobotNames()
        assert not NO_ACCESS_USER + "+bender" in robots

    def test_regenerate(self):
        self.login(NO_ACCESS_USER)

        # Create a robot.
        json = self.putJsonResponse(
            UserRobot, params=dict(robot_shortname="bender"), expected_code=201
        )

        token = json["token"]

        # Regenerate the robot.
        json = self.postJsonResponse(
            RegenerateUserRobot, params=dict(robot_shortname="bender"), expected_code=200
        )

        # Verify the token changed.
        self.assertNotEqual(token, json["token"])

        json2 = self.getJsonResponse(
            UserRobot, params=dict(robot_shortname="bender"), expected_code=200
        )

        self.assertEqual(json["token"], json2["token"])


class TestOrgRobots(ApiTestCase):
    def getRobotNames(self, include_permissions=False):
        params = dict(orgname=ORGANIZATION, permissions=include_permissions)
        return [r["name"] for r in self.getJsonResponse(OrgRobotList, params=params)["robots"]]

    def test_create_robot_with_underscores(self):
        self.login(ADMIN_ACCESS_USER)

        # Create the robot.
        self.putJsonResponse(
            OrgRobot,
            params=dict(orgname=ORGANIZATION, robot_shortname="mr_bender"),
            expected_code=201,
        )

        # Add the robot to a team.
        membername = ORGANIZATION + "+mr_bender"
        self.putJsonResponse(
            TeamMember, params=dict(orgname=ORGANIZATION, teamname="readers", membername=membername)
        )

        # Retrieve the robot's details.
        self.getJsonResponse(
            OrgRobot,
            params=dict(orgname=ORGANIZATION, robot_shortname="mr_bender"),
            expected_code=200,
        )

        # Make sure the robot shows up in the org robots list.
        self.assertTrue(membername in self.getRobotNames(include_permissions=True))

    def test_delete_robot_after_use(self):
        self.login(ADMIN_ACCESS_USER)

        # Create the robot.
        self.putJsonResponse(
            OrgRobot, params=dict(orgname=ORGANIZATION, robot_shortname="bender"), expected_code=201
        )

        # Add the robot to a team.
        membername = ORGANIZATION + "+bender"
        self.putJsonResponse(
            TeamMember, params=dict(orgname=ORGANIZATION, teamname="readers", membername=membername)
        )

        # Add a repository permission.
        self.putJsonResponse(
            RepositoryUserPermission,
            params=dict(repository=ORGANIZATION + "/" + ORG_REPO, username=membername),
            data=dict(role="read"),
        )

        # Add a permission prototype with the robot as the activating user.
        self.postJsonResponse(
            PermissionPrototypeList,
            params=dict(orgname=ORGANIZATION),
            data=dict(
                role="read",
                activating_user={"name": membername},
                delegate={"kind": "user", "name": membername},
            ),
        )

        # Add a permission prototype with the robot as the delegating user.
        self.postJsonResponse(
            PermissionPrototypeList,
            params=dict(orgname=ORGANIZATION),
            data=dict(role="read", delegate={"kind": "user", "name": membername}),
        )

        # Add a build trigger with the robot as the pull robot.
        database.BuildTriggerService.create(name="fakeservice")

        # Add a new fake trigger.
        repo = model.repository.get_repository(ORGANIZATION, ORG_REPO)
        user = model.user.get_user(ADMIN_ACCESS_USER)
        pull_robot = model.user.get_user(membername)
        trigger = model.build.create_build_trigger(
            repo, "fakeservice", "sometoken", user, pull_robot=pull_robot
        )

        # Add a fake build of the fake build trigger.
        token = model.token.create_access_token(
            repo, "write", kind="build-worker", friendly_name="Repository Build Token"
        )

        build = model.build.create_repository_build(
            repo, token, {}, "fake-dockerfile", "fake-name", trigger, pull_robot_name=membername
        )

        # Add some log entries for the robot.
        logs_model.log_action("pull_repo", ORGANIZATION, performer=pull_robot, repository=repo)

        # Delete the robot and verify it works.
        self.deleteEmptyResponse(
            OrgRobot, params=dict(orgname=ORGANIZATION, robot_shortname="bender")
        )

        # Verify the build is still present.
        self.assertIsNotNone(model.build.get_repository_build(build.uuid))

        # All the above records should now be deleted, along with the robot. We verify a few of the
        # critical ones below.

        # Check the team.
        team = model.team.get_organization_team(ORGANIZATION, "readers")
        members = [
            member.username for member in model.organization.get_organization_team_members(team.id)
        ]
        self.assertFalse(membername in members)

        # Check the robot itself.
        self.assertIsNone(model.user.get_user(membername))

    def test_robots(self):
        self.login(ADMIN_ACCESS_USER)

        # Create a robot.
        json = self.putJsonResponse(
            OrgRobot, params=dict(orgname=ORGANIZATION, robot_shortname="bender"), expected_code=201
        )

        self.assertEqual(ORGANIZATION + "+bender", json["name"])

        # Verify.
        robots = self.getRobotNames()
        assert ORGANIZATION + "+bender" in robots

        # Delete the robot.
        self.deleteEmptyResponse(
            OrgRobot, params=dict(orgname=ORGANIZATION, robot_shortname="bender")
        )

        # Verify.
        robots = self.getRobotNames()
        assert not ORGANIZATION + "+bender" in robots

    def test_regenerate(self):
        self.login(ADMIN_ACCESS_USER)

        # Create a robot.
        json = self.putJsonResponse(
            OrgRobot, params=dict(orgname=ORGANIZATION, robot_shortname="bender"), expected_code=201
        )

        token = json["token"]

        # Regenerate the robot.
        json = self.postJsonResponse(
            RegenerateOrgRobot,
            params=dict(orgname=ORGANIZATION, robot_shortname="bender"),
            expected_code=200,
        )

        # Verify the token changed.
        self.assertNotEqual(token, json["token"])

        json2 = self.getJsonResponse(
            OrgRobot, params=dict(orgname=ORGANIZATION, robot_shortname="bender"), expected_code=200
        )

        self.assertEqual(json["token"], json2["token"])


class TestLogs(ApiTestCase):
    def test_repo_logs(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(RepositoryLogs, params=dict(repository="devtable/simple"))
        assert "logs" in json
        assert "start_time" in json
        assert "end_time" in json

    def test_repo_logs_crossyear(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(
            RepositoryLogs,
            params=dict(repository="devtable/simple", starttime="12/01/2016", endtime="1/09/2017"),
        )
        self.assertEqual("Thu, 01 Dec 2016 00:00:00 -0000", json["start_time"])
        self.assertEqual("Tue, 10 Jan 2017 00:00:00 -0000", json["end_time"])

    def test_repo_aggregate_logs(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(
            RepositoryAggregateLogs, params=dict(repository="devtable/simple")
        )
        assert "aggregated" in json
        assert len(json["aggregated"]) > 0

    def test_user_logs(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(UserLogs)
        assert "logs" in json
        assert "start_time" in json
        assert "end_time" in json

    def test_org_logs(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(OrgLogs, params=dict(orgname=ORGANIZATION))
        assert "logs" in json
        assert "start_time" in json
        assert "end_time" in json

    def test_user_aggregate_logs(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(UserAggregateLogs)
        assert "aggregated" in json

    def test_org_aggregate_logs(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(OrgAggregateLogs, params=dict(orgname=ORGANIZATION))
        assert "aggregated" in json

    def test_performer(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(OrgLogs, params=dict(orgname=ORGANIZATION))
        all_logs = json["logs"]

        json = self.getJsonResponse(
            OrgLogs, params=dict(performer=READ_ACCESS_USER, orgname=ORGANIZATION)
        )

        assert len(json["logs"]) < len(all_logs)
        for log in json["logs"]:
            self.assertEqual(READ_ACCESS_USER, log["performer"]["name"])


class TestApplicationInformation(ApiTestCase):
    def test_get_info(self):
        json = self.getJsonResponse(
            ApplicationInformation, params=dict(client_id=FAKE_APPLICATION_CLIENT_ID)
        )
        assert "name" in json
        assert "uri" in json
        assert "organization" in json

    def test_get_invalid_info(self):
        self.getJsonResponse(
            ApplicationInformation, params=dict(client_id="invalid-code"), expected_code=404
        )


class TestOrganizationApplications(ApiTestCase):
    def test_list_create_applications(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(OrganizationApplications, params=dict(orgname=ORGANIZATION))

        self.assertEqual(2, len(json["applications"]))

        found = False
        for application in json["applications"]:
            if application["client_id"] == FAKE_APPLICATION_CLIENT_ID:
                found = True
                break

        self.assertTrue(found)

        # Add a new application.
        json = self.postJsonResponse(
            OrganizationApplications,
            params=dict(orgname=ORGANIZATION),
            data=dict(name="Some cool app", description="foo"),
        )

        self.assertEqual("Some cool app", json["name"])
        self.assertEqual("foo", json["description"])

        # Retrieve the apps list again
        list_json = self.getJsonResponse(
            OrganizationApplications, params=dict(orgname=ORGANIZATION)
        )
        self.assertEqual(3, len(list_json["applications"]))


class TestOrganizationApplicationResource(ApiTestCase):
    def test_get_edit_delete_application(self):
        self.login(ADMIN_ACCESS_USER)

        # Retrieve the application.
        json = self.getJsonResponse(
            OrganizationApplicationResource,
            params=dict(orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID),
        )

        self.assertEqual(FAKE_APPLICATION_CLIENT_ID, json["client_id"])

        # Edit the application.
        edit_json = self.putJsonResponse(
            OrganizationApplicationResource,
            params=dict(orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID),
            data=dict(
                name="Some App",
                description="foo",
                application_uri="bar",
                redirect_uri="baz",
                avatar_email="meh",
            ),
        )

        self.assertEqual(FAKE_APPLICATION_CLIENT_ID, edit_json["client_id"])
        self.assertEqual("Some App", edit_json["name"])
        self.assertEqual("foo", edit_json["description"])
        self.assertEqual("bar", edit_json["application_uri"])
        self.assertEqual("baz", edit_json["redirect_uri"])
        self.assertEqual("meh", edit_json["avatar_email"])

        # Retrieve the application again.
        json = self.getJsonResponse(
            OrganizationApplicationResource,
            params=dict(orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID),
        )

        self.assertEqual(json, edit_json)

        # Delete the application.
        self.deleteEmptyResponse(
            OrganizationApplicationResource,
            params=dict(orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID),
        )

        # Make sure the application is gone.
        self.getJsonResponse(
            OrganizationApplicationResource,
            params=dict(orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID),
            expected_code=404,
        )


class TestOrganization(ApiTestCase):
    def test_change_send_billing_invoice(self):
        self.login(ADMIN_ACCESS_USER)
        self.putJsonResponse(
            Organization,
            params=dict(orgname=ORGANIZATION),
            data=dict(invoice_email=False, invoice_email_address=None),
        )


class TestOrganizationApplicationResetClientSecret(ApiTestCase):
    def test_reset_client_secret(self):
        self.login(ADMIN_ACCESS_USER)

        # Retrieve the application.
        json = self.getJsonResponse(
            OrganizationApplicationResource,
            params=dict(orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID),
        )

        self.assertEqual(FAKE_APPLICATION_CLIENT_ID, json["client_id"])

        # Reset the client secret.
        reset_json = self.postJsonResponse(
            OrganizationApplicationResetClientSecret,
            params=dict(orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID),
        )

        self.assertEqual(FAKE_APPLICATION_CLIENT_ID, reset_json["client_id"])
        self.assertNotEqual(reset_json["client_secret"], json["client_secret"])

        # Verify it was changed in the DB.
        json = self.getJsonResponse(
            OrganizationApplicationResource,
            params=dict(orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID),
        )
        self.assertEqual(reset_json["client_secret"], json["client_secret"])


class FakeBuildTrigger(BuildTriggerHandler):
    @classmethod
    def service_name(cls):
        return "fakeservice"

    def list_build_source_namespaces(self):
        return [
            {"name": "first", "id": "first"},
            {"name": "second", "id": "second"},
        ]

    def list_build_sources_for_namespace(self, namespace):
        if namespace == "first":
            return [
                {
                    "name": "source",
                }
            ]
        elif namespace == "second":
            return [
                {
                    "name": self.auth_token,
                }
            ]
        else:
            return []

    def list_build_subdirs(self):
        return [self.auth_token, "foo", "bar", self.config["somevalue"]]

    def handle_trigger_request(self, request):
        prepared = PreparedBuild(self.trigger)
        prepared.build_name = "build-name"
        prepared.tags = ["bar"]
        prepared.dockerfile_id = "foo"
        prepared.subdirectory = "subdir"
        prepared.metadata = {"foo": "bar"}
        prepared.is_manual = False
        return prepared

    def is_active(self):
        return "active" in self.config and self.config["active"]

    def activate(self, standard_webhook_url):
        self.config["active"] = True
        return self.config, {}

    def deactivate(self):
        self.config["active"] = False
        return self.config

    def manual_start(self, run_parameters=None):
        prepared = PreparedBuild(self.trigger)
        prepared.build_name = "build-name"
        prepared.tags = ["bar"]
        prepared.dockerfile_id = "foo"
        prepared.subdirectory = "subdir"
        prepared.metadata = {"foo": "bar"}
        prepared.is_manual = True
        prepared.context = "/"
        return prepared

    def get_repository_url(self):
        return "http://foo/" + self.config["build_source"]

    def load_dockerfile_contents(self):
        if not "dockerfile" in self.config:
            return None

        return self.config["dockerfile"]

    def list_field_values(self, field_name, limit=None):
        if field_name == "test_field":
            return [1, 2, 3]

        return None


class TestBuildTriggers(ApiTestCase):
    def test_list_build_triggers(self):
        self.login(ADMIN_ACCESS_USER)

        # Check a repo with no known triggers.
        json = self.getJsonResponse(
            BuildTriggerList, params=dict(repository=ADMIN_ACCESS_USER + "/simple")
        )
        self.assertEqual(0, len(json["triggers"]))

        # Check a repo with one known trigger.
        json = self.getJsonResponse(
            BuildTriggerList, params=dict(repository=ADMIN_ACCESS_USER + "/building")
        )
        self.assertEqual(1, len(json["triggers"]))

        trigger = json["triggers"][0]

        assert "id" in trigger
        assert "is_active" in trigger
        assert "config" in trigger
        assert "service" in trigger

        # Verify the get trigger method.
        trigger_json = self.getJsonResponse(
            BuildTrigger,
            params=dict(repository=ADMIN_ACCESS_USER + "/building", trigger_uuid=trigger["id"]),
        )

        self.assertEqual(trigger, trigger_json)

        # Check the recent builds for the trigger.
        builds_json = self.getJsonResponse(
            TriggerBuildList,
            params=dict(repository=ADMIN_ACCESS_USER + "/building", trigger_uuid=trigger["id"]),
        )

        assert "builds" in builds_json

    def test_delete_build_trigger(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(
            BuildTriggerList, params=dict(repository=ADMIN_ACCESS_USER + "/building")
        )
        self.assertEqual(1, len(json["triggers"]))
        trigger = json["triggers"][0]

        # Delete the trigger.
        self.deleteEmptyResponse(
            BuildTrigger,
            params=dict(repository=ADMIN_ACCESS_USER + "/building", trigger_uuid=trigger["id"]),
        )

        # Verify it was deleted.
        json = self.getJsonResponse(
            BuildTriggerList, params=dict(repository=ADMIN_ACCESS_USER + "/building")
        )
        self.assertEqual(0, len(json["triggers"]))

    def test_analyze_fake_trigger(self):
        self.login(ADMIN_ACCESS_USER)

        database.BuildTriggerService.create(name="fakeservice")

        # Add a new fake trigger.
        repo = model.repository.get_repository(ADMIN_ACCESS_USER, "simple")
        user = model.user.get_user(ADMIN_ACCESS_USER)
        trigger = model.build.create_build_trigger(repo, "fakeservice", "sometoken", user)

        # Analyze the trigger's dockerfile: First, no dockerfile.
        trigger_config = {}
        analyze_json = self.postJsonResponse(
            BuildTriggerAnalyze,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data={"config": trigger_config},
        )

        self.assertEqual("warning", analyze_json["status"])
        self.assertEqual(
            "Specified Dockerfile path for the trigger was not "
            + "found on the main branch. This trigger may fail.",
            analyze_json["message"],
        )

        # Analyze the trigger's dockerfile: Second, missing FROM in dockerfile.
        trigger_config = {"dockerfile": "MAINTAINER me"}
        analyze_json = self.postJsonResponse(
            BuildTriggerAnalyze,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data={"config": trigger_config},
        )

        self.assertEqual("warning", analyze_json["status"])
        self.assertEqual("No FROM line found in the Dockerfile", analyze_json["message"])

        # Analyze the trigger's dockerfile: Third, dockerfile with public repo.
        trigger_config = {"dockerfile": "FROM somerepo"}
        analyze_json = self.postJsonResponse(
            BuildTriggerAnalyze,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data={"config": trigger_config},
        )

        self.assertEqual("publicbase", analyze_json["status"])

        # Analyze the trigger's dockerfile: Fourth, dockerfile with private repo with an invalid path.
        trigger_config = {"dockerfile": "FROM localhost:5000/somepath"}
        analyze_json = self.postJsonResponse(
            BuildTriggerAnalyze,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data={"config": trigger_config},
        )

        self.assertEqual("warning", analyze_json["status"])
        self.assertEqual(
            '"localhost:5000/somepath" is not a valid Quay repository path', analyze_json["message"]
        )

        # Analyze the trigger's dockerfile: Fifth, dockerfile with private repo that does not exist.
        trigger_config = {"dockerfile": "FROM localhost:5000/nothere/randomrepo"}
        analyze_json = self.postJsonResponse(
            BuildTriggerAnalyze,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data={"config": trigger_config},
        )

        self.assertEqual("error", analyze_json["status"])
        nofound = (
            'Repository "localhost:5000/%s/randomrepo" referenced by the Dockerfile was not found'
        )
        self.assertEqual(nofound % "nothere", analyze_json["message"])

        # Analyze the trigger's dockerfile: Sixth, dockerfile with private repo that the user cannot see
        trigger_config = {"dockerfile": "FROM localhost:5000/randomuser/randomrepo"}
        analyze_json = self.postJsonResponse(
            BuildTriggerAnalyze,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data={"config": trigger_config},
        )

        self.assertEqual("error", analyze_json["status"])
        self.assertEqual(nofound % "randomuser", analyze_json["message"])

        # Analyze the trigger's dockerfile: Seventh, dockerfile with private repo that the user see.
        trigger_config = {"dockerfile": "FROM localhost:5000/devtable/complex"}
        analyze_json = self.postJsonResponse(
            BuildTriggerAnalyze,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data={"config": trigger_config},
        )

        self.assertEqual("requiresrobot", analyze_json["status"])
        self.assertEqual("devtable", analyze_json["namespace"])
        self.assertEqual("complex", analyze_json["name"])
        self.assertEqual(ADMIN_ACCESS_USER + "+dtrobot", analyze_json["robots"][0]["name"])

    def test_fake_trigger(self):
        self.login(ADMIN_ACCESS_USER)

        database.BuildTriggerService.create(name="fakeservice")

        # Add a new fake trigger.
        repo = model.repository.get_repository(ADMIN_ACCESS_USER, "simple")
        user = model.user.get_user(ADMIN_ACCESS_USER)
        trigger = model.build.create_build_trigger(repo, "fakeservice", "sometoken", user)

        # Verify the trigger.
        json = self.getJsonResponse(
            BuildTriggerList, params=dict(repository=ADMIN_ACCESS_USER + "/simple")
        )
        self.assertEqual(1, len(json["triggers"]))
        self.assertEqual(trigger.uuid, json["triggers"][0]["id"])
        self.assertEqual(trigger.service.name, json["triggers"][0]["service"])
        self.assertEqual(False, json["triggers"][0]["is_active"])

        # List the trigger's source namespaces.
        namespace_json = self.getJsonResponse(
            BuildTriggerSourceNamespaces,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
        )
        self.assertEqual(
            [{"id": "first", "name": "first"}, {"id": "second", "name": "second"}],
            namespace_json["namespaces"],
        )

        source_json = self.postJsonResponse(
            BuildTriggerSources,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data=dict(namespace="first"),
        )
        self.assertEqual([{"name": "source"}], source_json["sources"])

        # List the trigger's subdirs.
        subdir_json = self.postJsonResponse(
            BuildTriggerSubdirs,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data={"somevalue": "meh"},
        )

        self.assertEqual(
            {
                "status": "success",
                "dockerfile_paths": ["/sometoken", "/foo", "/bar", "/meh"],
                "contextMap": {"/bar": ["/"], "/foo": ["/"], "/meh": ["/"], "/sometoken": ["/"]},
            },
            subdir_json,
        )

        # Activate the trigger.
        trigger_config = {"build_source": "somesource"}
        activate_json = self.postJsonResponse(
            BuildTriggerActivate,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data={"config": trigger_config},
        )

        self.assertEqual(True, activate_json["is_active"])

        # Make sure the trigger has a write token.
        trigger = model.build.get_build_trigger(trigger.uuid)
        self.assertNotEqual(None, trigger.write_token)
        self.assertEqual(True, py_json.loads(trigger.config)["active"])

        # Make sure we cannot activate again.
        self.postResponse(
            BuildTriggerActivate,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data={"config": trigger_config},
            expected_code=400,
        )

        # Retrieve values for a field.
        result = self.postJsonResponse(
            BuildTriggerFieldValues,
            params=dict(
                repository=ADMIN_ACCESS_USER + "/simple",
                trigger_uuid=trigger.uuid,
                field_name="test_field",
            ),
        )

        self.assertEqual(result["values"], [1, 2, 3])

        self.postResponse(
            BuildTriggerFieldValues,
            params=dict(
                repository=ADMIN_ACCESS_USER + "/simple",
                trigger_uuid=trigger.uuid,
                field_name="another_field",
            ),
            expected_code=404,
        )

        # Start a manual build.
        start_json = self.postJsonResponse(
            ActivateBuildTrigger,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data=dict(),
            expected_code=201,
        )

        assert "id" in start_json
        self.assertEqual("build-name", start_json["display_name"])
        self.assertEqual(["bar"], start_json["tags"])
        self.assertEqual("subdir", start_json["subdirectory"])
        self.assertEqual("somesource", start_json["trigger"]["build_source"])

        # Verify the metadata was added.
        build_obj = database.RepositoryBuild.get(database.RepositoryBuild.uuid == start_json["id"])
        self.assertEqual("bar", py_json.loads(build_obj.job_config)["trigger_metadata"]["foo"])

        # Start another manual build, with a ref.
        self.postJsonResponse(
            ActivateBuildTrigger,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data=dict(refs={"kind": "branch", "name": "foobar"}),
            expected_code=201,
        )

        # Start another manual build with a null ref.
        self.postJsonResponse(
            ActivateBuildTrigger,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data=dict(refs=None),
            expected_code=201,
        )

    def test_invalid_robot_account(self):
        self.login(ADMIN_ACCESS_USER)

        database.BuildTriggerService.create(name="fakeservice")

        # Add a new fake trigger.
        repo = model.repository.get_repository(ADMIN_ACCESS_USER, "simple")
        user = model.user.get_user(ADMIN_ACCESS_USER)
        trigger = model.build.create_build_trigger(repo, "fakeservice", "sometoken", user)

        # Try to activate it with an invalid robot account.
        trigger_config = {}
        self.postJsonResponse(
            BuildTriggerActivate,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data={"config": trigger_config, "pull_robot": "someinvalidrobot"},
            expected_code=404,
        )

    def test_unauthorized_robot_account(self):
        self.login(ADMIN_ACCESS_USER)

        database.BuildTriggerService.create(name="fakeservice")

        # Add a new fake trigger.
        repo = model.repository.get_repository(ADMIN_ACCESS_USER, "simple")
        user = model.user.get_user(ADMIN_ACCESS_USER)
        trigger = model.build.create_build_trigger(repo, "fakeservice", "sometoken", user)

        # Try to activate it with a robot account in the wrong namespace.
        trigger_config = {}
        self.postJsonResponse(
            BuildTriggerActivate,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data={"config": trigger_config, "pull_robot": "freshuser+anotherrobot"},
            expected_code=403,
        )

    def test_robot_account(self):
        self.login(ADMIN_ACCESS_USER)

        database.BuildTriggerService.create(name="fakeservice")

        # Add a new fake trigger.
        repo = model.repository.get_repository(ADMIN_ACCESS_USER, "simple")
        user = model.user.get_user(ADMIN_ACCESS_USER)
        trigger = model.build.create_build_trigger(repo, "fakeservice", "sometoken", user)

        # Try to activate it with a robot account.
        trigger_config = {}
        activate_json = self.postJsonResponse(
            BuildTriggerActivate,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data={"config": trigger_config, "pull_robot": ADMIN_ACCESS_USER + "+dtrobot"},
        )

        # Verify that the robot was saved.
        self.assertEqual(True, activate_json["is_active"])
        self.assertEqual(ADMIN_ACCESS_USER + "+dtrobot", activate_json["pull_robot"]["name"])

        # Start a manual build.
        start_json = self.postJsonResponse(
            ActivateBuildTrigger,
            params=dict(repository=ADMIN_ACCESS_USER + "/simple", trigger_uuid=trigger.uuid),
            data=dict(refs=dict(kind="branch", name="foobar")),
            expected_code=201,
        )

        assert "id" in start_json
        self.assertEqual("build-name", start_json["display_name"])
        self.assertEqual(["bar"], start_json["tags"])


class TestUserAuthorizations(ApiTestCase):
    def test_list_get_delete_user_authorizations(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(UserAuthorizationList)

        self.assertEqual(1, len(json["authorizations"]))

        authorization = json["authorizations"][0]

        assert "uuid" in authorization
        assert "scopes" in authorization
        assert "application" in authorization

        # Retrieve the authorization.
        get_json = self.getJsonResponse(
            UserAuthorization, params=dict(access_token_uuid=authorization["uuid"])
        )
        self.assertEqual(authorization, get_json)

        # Delete the authorization.
        self.deleteEmptyResponse(
            UserAuthorization, params=dict(access_token_uuid=authorization["uuid"])
        )

        # Verify it has been deleted.
        self.getJsonResponse(
            UserAuthorization,
            params=dict(access_token_uuid=authorization["uuid"]),
            expected_code=404,
        )


class TestSuperUserLogs(ApiTestCase):
    def test_get_logs(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(SuperUserLogs)

        assert "logs" in json
        assert len(json["logs"]) > 0


class TestSuperUserTakeOwnership(ApiTestCase):
    def test_take_ownership_superuser(self):
        self.login(ADMIN_ACCESS_USER)

        # Should fail to take ownership of a superuser.
        self.postResponse(
            SuperUserTakeOwnership, params=dict(namespace=ADMIN_ACCESS_USER), expected_code=400
        )

    def test_take_ownership_invalid_namespace(self):
        self.login(ADMIN_ACCESS_USER)
        self.postResponse(
            SuperUserTakeOwnership, params=dict(namespace="invalid"), expected_code=404
        )

    def test_take_ownership_non_superuser(self):
        self.login(READ_ACCESS_USER)
        self.postResponse(
            SuperUserTakeOwnership, params=dict(namespace="freshuser"), expected_code=403
        )

    def test_take_ownership_user(self):
        self.login(ADMIN_ACCESS_USER)

        with assert_action_logged("take_ownership"):
            # Take ownership of the read user.
            self.postResponse(SuperUserTakeOwnership, params=dict(namespace=READ_ACCESS_USER))

            # Ensure that the read access user is now an org, with the superuser as the owner.
            reader = model.user.get_user_or_org(READ_ACCESS_USER)
            self.assertTrue(reader.organization)

            usernames = [admin.username for admin in model.organization.get_admin_users(reader)]
            self.assertIn(ADMIN_ACCESS_USER, usernames)

    def test_take_ownership_org(self):
        # Create a new org with another user as owner.
        public_user = model.user.get_user(PUBLIC_USER)
        org = model.organization.create_organization("someorg", "some@example.com", public_user)

        # Ensure that the admin is not yet owner of the org.
        usernames = [admin.username for admin in model.organization.get_admin_users(org)]
        self.assertNotIn(ADMIN_ACCESS_USER, usernames)

        with assert_action_logged("take_ownership"):
            # Take ownership.
            self.login(ADMIN_ACCESS_USER)
            self.postResponse(SuperUserTakeOwnership, params=dict(namespace="someorg"))

            # Ensure now in the admin users.
            usernames = [admin.username for admin in model.organization.get_admin_users(org)]
            self.assertIn(ADMIN_ACCESS_USER, usernames)


class TestSuperUserKeyManagement(ApiTestCase):
    def test_get_update_keys(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(SuperUserServiceKeyManagement)
        key_count = len(json["keys"])

        key = json["keys"][0]
        self.assertTrue("name" in key)
        self.assertTrue("service" in key)
        self.assertTrue("kid" in key)
        self.assertTrue("created_date" in key)
        self.assertTrue("expiration_date" in key)
        self.assertTrue("jwk" in key)
        self.assertTrue("approval" in key)
        self.assertTrue("metadata" in key)

        with assert_action_logged("service_key_modify"):
            # Update the key's name.
            self.putJsonResponse(
                SuperUserServiceKey, params=dict(kid=key["kid"]), data=dict(name="somenewname")
            )

            # Ensure the key's name has been changed.
            json = self.getJsonResponse(SuperUserServiceKey, params=dict(kid=key["kid"]))
            self.assertEqual("somenewname", json["name"])

        with assert_action_logged("service_key_modify"):
            # Update the key's metadata.
            self.putJsonResponse(
                SuperUserServiceKey,
                params=dict(kid=key["kid"]),
                data=dict(metadata=dict(foo="bar")),
            )

            # Ensure the key's metadata has been changed.
            json = self.getJsonResponse(SuperUserServiceKey, params=dict(kid=key["kid"]))
            self.assertEqual("bar", json["metadata"]["foo"])

        with assert_action_logged("service_key_extend"):
            # Change the key's expiration.
            self.putJsonResponse(
                SuperUserServiceKey, params=dict(kid=key["kid"]), data=dict(expiration=None)
            )

            # Ensure the key's expiration has been changed.
            json = self.getJsonResponse(SuperUserServiceKey, params=dict(kid=key["kid"]))
            self.assertIsNone(json["expiration_date"])

        with assert_action_logged("service_key_delete"):
            # Delete the key.
            self.deleteEmptyResponse(SuperUserServiceKey, params=dict(kid=key["kid"]))

            # Ensure the key no longer exists.
            self.getResponse(SuperUserServiceKey, params=dict(kid=key["kid"]), expected_code=404)

            json = self.getJsonResponse(SuperUserServiceKeyManagement)
            self.assertEqual(key_count - 1, len(json["keys"]))

    def test_approve_key(self):
        self.login(ADMIN_ACCESS_USER)

        # Ensure the key is not yet approved.
        json = self.getJsonResponse(SuperUserServiceKey, params=dict(kid="kid3"))
        self.assertEqual("unapprovedkey", json["name"])
        self.assertIsNone(json["approval"])

        # Approve the key.
        with assert_action_logged("service_key_approve"):
            self.postResponse(
                SuperUserServiceKeyApproval,
                params=dict(kid="kid3"),
                data=dict(notes="testapprove"),
                expected_code=201,
            )

            # Ensure the key is approved.
            json = self.getJsonResponse(SuperUserServiceKey, params=dict(kid="kid3"))
            self.assertEqual("unapprovedkey", json["name"])
            self.assertIsNotNone(json["approval"])
            self.assertEqual("ServiceKeyApprovalType.SUPERUSER", json["approval"]["approval_type"])
            self.assertEqual(ADMIN_ACCESS_USER, json["approval"]["approver"]["username"])
            self.assertEqual("testapprove", json["approval"]["notes"])

    def test_approve_preapproved(self):
        self.login(ADMIN_ACCESS_USER)

        new_key = {
            "service": "coolservice",
            "name": "mynewkey",
            "metadata": dict(foo="baz"),
            "notes": "whazzup!?",
            "expiration": timegm(
                (datetime.datetime.now() + datetime.timedelta(days=1)).utctimetuple()
            ),
        }

        # Create the key (preapproved automatically)
        json = self.postJsonResponse(SuperUserServiceKeyManagement, data=new_key)

        # Try to approve again.
        self.postResponse(
            SuperUserServiceKeyApproval, params=dict(kid=json["kid"]), expected_code=201
        )

    def test_create_key(self):
        self.login(ADMIN_ACCESS_USER)

        new_key = {
            "service": "coolservice",
            "name": "mynewkey",
            "metadata": dict(foo="baz"),
            "notes": "whazzup!?",
            "expiration": timegm(
                (datetime.datetime.now() + datetime.timedelta(days=1)).utctimetuple()
            ),
        }

        with assert_action_logged("service_key_create"):
            # Create the key.
            json = self.postJsonResponse(SuperUserServiceKeyManagement, data=new_key)
            self.assertEqual("mynewkey", json["name"])
            self.assertTrue("kid" in json)
            self.assertTrue("public_key" in json)
            self.assertTrue("private_key" in json)

            # Verify the private key is a valid PEM.
            serialization.load_pem_private_key(
                json["private_key"].encode("utf-8"), None, default_backend()
            )

            # Verify the key.
            kid = json["kid"]

            json = self.getJsonResponse(SuperUserServiceKey, params=dict(kid=kid))
            self.assertEqual("mynewkey", json["name"])
            self.assertEqual("coolservice", json["service"])
            self.assertEqual("baz", json["metadata"]["foo"])
            self.assertEqual(kid, json["kid"])

            self.assertIsNotNone(json["approval"])
            self.assertEqual("ServiceKeyApprovalType.SUPERUSER", json["approval"]["approval_type"])
            self.assertEqual(ADMIN_ACCESS_USER, json["approval"]["approver"]["username"])
            self.assertEqual("whazzup!?", json["approval"]["notes"])


class TestRepositoryManifestLabels(ApiTestCase):
    def test_basic_labels(self):
        self.login(ADMIN_ACCESS_USER)

        repo_ref = registry_model.lookup_repository(ADMIN_ACCESS_USER, "complex")
        tag = registry_model.get_repo_tag(repo_ref, "prod")
        repository = ADMIN_ACCESS_USER + "/complex"

        # Check the existing labels on the complex repo, which should be empty
        json = self.getJsonResponse(
            RepositoryManifestLabels,
            params=dict(repository=repository, manifestref=tag.manifest_digest),
        )

        self.assertEqual(0, len(json["labels"]))

        self.postJsonResponse(
            RepositoryManifestLabels,
            params=dict(repository=repository, manifestref=tag.manifest_digest),
            data=dict(key="bad_label", value="world", media_type="text/plain"),
            expected_code=400,
        )

        self.postJsonResponse(
            RepositoryManifestLabels,
            params=dict(repository=repository, manifestref=tag.manifest_digest),
            data=dict(key="hello", value="world", media_type="bad_media_type"),
            expected_code=400,
        )

        # Add some labels to the manifest.
        with assert_action_logged("manifest_label_add"):
            label1 = self.postJsonResponse(
                RepositoryManifestLabels,
                params=dict(repository=repository, manifestref=tag.manifest_digest),
                data=dict(key="hello", value="world", media_type="text/plain"),
                expected_code=201,
            )

        with assert_action_logged("manifest_label_add"):
            label2 = self.postJsonResponse(
                RepositoryManifestLabels,
                params=dict(repository=repository, manifestref=tag.manifest_digest),
                data=dict(key="hi", value="there", media_type="text/plain"),
                expected_code=201,
            )

        with assert_action_logged("manifest_label_add"):
            label3 = self.postJsonResponse(
                RepositoryManifestLabels,
                params=dict(repository=repository, manifestref=tag.manifest_digest),
                data=dict(key="hello", value="someone", media_type="application/json"),
                expected_code=201,
            )

        # Ensure we have *3* labels
        json = self.getJsonResponse(
            RepositoryManifestLabels,
            params=dict(repository=repository, manifestref=tag.manifest_digest),
        )

        self.assertEqual(3, len(json["labels"]))

        self.assertNotEqual(label2["label"]["id"], label1["label"]["id"])
        self.assertNotEqual(label3["label"]["id"], label1["label"]["id"])
        self.assertNotEqual(label2["label"]["id"], label3["label"]["id"])

        self.assertEqual("text/plain", label1["label"]["media_type"])
        self.assertEqual("text/plain", label2["label"]["media_type"])
        self.assertEqual("application/json", label3["label"]["media_type"])

        # Ensure we can retrieve each of the labels.
        for label in json["labels"]:
            label_json = self.getJsonResponse(
                ManageRepositoryManifestLabel,
                params=dict(
                    repository=repository, manifestref=tag.manifest_digest, labelid=label["id"]
                ),
            )
            self.assertEqual(label["id"], label_json["id"])

        # Delete a label.
        with assert_action_logged("manifest_label_delete"):
            self.deleteEmptyResponse(
                ManageRepositoryManifestLabel,
                params=dict(
                    repository=repository,
                    manifestref=tag.manifest_digest,
                    labelid=label1["label"]["id"],
                ),
            )

        # Ensure the label is gone.
        json = self.getJsonResponse(
            RepositoryManifestLabels,
            params=dict(repository=repository, manifestref=tag.manifest_digest),
        )

        self.assertEqual(2, len(json["labels"]))

        # Check filtering.
        json = self.getJsonResponse(
            RepositoryManifestLabels,
            params=dict(repository=repository, manifestref=tag.manifest_digest, filter="hello"),
        )

        self.assertEqual(1, len(json["labels"]))

    def test_prefixed_labels(self):
        self.login(ADMIN_ACCESS_USER)

        repo_ref = registry_model.lookup_repository(ADMIN_ACCESS_USER, "complex")
        tag = registry_model.get_repo_tag(repo_ref, "prod")
        repository = ADMIN_ACCESS_USER + "/complex"

        self.postJsonResponse(
            RepositoryManifestLabels,
            params=dict(repository=repository, manifestref=tag.manifest_digest),
            data=dict(key="com.dockers.whatever", value="pants", media_type="text/plain"),
            expected_code=201,
        )

        self.postJsonResponse(
            RepositoryManifestLabels,
            params=dict(repository=repository, manifestref=tag.manifest_digest),
            data=dict(key="my.cool.prefix.for.my.label", value="value", media_type="text/plain"),
            expected_code=201,
        )

    def test_add_invalid_media_type(self):
        self.login(ADMIN_ACCESS_USER)

        repo_ref = registry_model.lookup_repository(ADMIN_ACCESS_USER, "complex")
        tag = registry_model.get_repo_tag(repo_ref, "prod")
        repository = ADMIN_ACCESS_USER + "/complex"

        self.postResponse(
            RepositoryManifestLabels,
            params=dict(repository=repository, manifestref=tag.manifest_digest),
            data=dict(key="hello", value="world", media_type="some/invalid"),
            expected_code=400,
        )

    def test_add_invalid_key(self):
        self.login(ADMIN_ACCESS_USER)

        repo_ref = registry_model.lookup_repository(ADMIN_ACCESS_USER, "complex")
        tag = registry_model.get_repo_tag(repo_ref, "prod")
        repository = ADMIN_ACCESS_USER + "/complex"

        # Try to add an empty label key.
        self.postResponse(
            RepositoryManifestLabels,
            params=dict(repository=repository, manifestref=tag.manifest_digest),
            data=dict(key="", value="world"),
            expected_code=400,
        )

        # Try to add an invalid label key.
        self.postResponse(
            RepositoryManifestLabels,
            params=dict(repository=repository, manifestref=tag.manifest_digest),
            data=dict(key="invalid___key", value="world"),
            expected_code=400,
        )

        # Try to add a label key in a reserved namespace.
        self.postResponse(
            RepositoryManifestLabels,
            params=dict(repository=repository, manifestref=tag.manifest_digest),
            data=dict(key="io.docker.whatever", value="world"),
            expected_code=400,
        )


class TestSuperUserManagement(ApiTestCase):
    def test_get_user(self):
        self.login(ADMIN_ACCESS_USER)

        json = self.getJsonResponse(SuperUserManagement, params=dict(username="freshuser"))
        self.assertEqual("freshuser", json["username"])
        self.assertEqual("jschorr+test@devtable.com", json["email"])
        self.assertEqual(False, json["super_user"])

    def test_delete_user(self):
        self.login(ADMIN_ACCESS_USER)

        # Verify the user exists.
        json = self.getJsonResponse(SuperUserManagement, params=dict(username="freshuser"))
        self.assertEqual("freshuser", json["username"])

        # Delete the user.
        self.deleteEmptyResponse(
            SuperUserManagement, params=dict(username="freshuser"), expected_code=204
        )

        # Verify the user no longer exists.
        self.getResponse(SuperUserManagement, params=dict(username="freshuser"), expected_code=404)

    def test_change_user_password(self):
        self.login(ADMIN_ACCESS_USER)

        # Verify the user exists.
        json = self.getJsonResponse(SuperUserManagement, params=dict(username="freshuser"))
        self.assertEqual("freshuser", json["username"])
        self.assertEqual("jschorr+test@devtable.com", json["email"])

        # Update the user.
        json = self.putJsonResponse(
            SuperUserManagement,
            params=dict(username="freshuser"),
            data=dict(password="somepassword"),
        )
        self.assertTrue("encrypted_password" in json)

    def test_update_user(self):
        self.login(ADMIN_ACCESS_USER)

        # Verify the user exists.
        json = self.getJsonResponse(SuperUserManagement, params=dict(username="freshuser"))
        self.assertEqual("freshuser", json["username"])
        self.assertEqual("jschorr+test@devtable.com", json["email"])

        # Update the user.
        json = self.putJsonResponse(
            SuperUserManagement, params=dict(username="freshuser"), data=dict(email="foo@bar.com")
        )
        self.assertFalse("encrypted_password" in json)

        # Verify the user was updated.
        json = self.getJsonResponse(SuperUserManagement, params=dict(username="freshuser"))
        self.assertEqual("freshuser", json["username"])
        self.assertEqual("foo@bar.com", json["email"])

    def test_set_message(self):
        self.login(ADMIN_ACCESS_USER)

        # Create a message
        message = {"content": "new message", "severity": "info", "media_type": "text/plain"}
        self.postResponse(GlobalUserMessages, data=dict(message=message), expected_code=201)

        json = self.getJsonResponse(GlobalUserMessages)
        self.assertEqual(len(json["messages"]), 3)

        has_matching_message = False
        for message in json["messages"]:
            new_message_match = message["content"] == "new message"
            severity_match = message["severity"] == "info"
            media_type_match = message["media_type"] == "text/plain"
            if new_message_match and severity_match and media_type_match:
                has_matching_message = True
                break

        self.assertTrue(
            has_matching_message, "Could not find matching message in: " + str(json["messages"])
        )
        self.assertNotEqual(json["messages"][0]["content"], json["messages"][2]["content"])
        self.assertTrue(json["messages"][2]["uuid"])

    def test_delete_message(self):
        self.login(ADMIN_ACCESS_USER)
        json = self.getJsonResponse(GlobalUserMessages)
        self.deleteEmptyResponse(GlobalUserMessage, {"uuid": json["messages"][0]["uuid"]}, 204)

        json = self.getJsonResponse(GlobalUserMessages)

        self.assertEqual(len(json["messages"]), 1)


class TestOrganizationRhSku(ApiTestCase):
    def test_bind_sku_to_org(self):
        self.login(SUBSCRIPTION_USER)
        self.postResponse(
            resource_name=OrganizationRhSku,
            params=dict(orgname=SUBSCRIPTION_ORG),
            data={"subscriptions": [{"subscription_id": 12345678}]},
            expected_code=201,
        )
        json = self.getJsonResponse(
            resource_name=OrganizationRhSku,
            params=dict(orgname=SUBSCRIPTION_ORG),
        )
        self.assertEqual(len(json), 1)

    def test_bind_sku_duplicate(self):
        user = model.user.get_user(SUBSCRIPTION_USER)
        org = model.organization.get_organization(SUBSCRIPTION_ORG)
        model.organization_skus.bind_subscription_to_org(12345678, org.id, user.id)
        self.login(SUBSCRIPTION_USER)
        self.postResponse(
            resource_name=OrganizationRhSku,
            params=dict(orgname=SUBSCRIPTION_ORG),
            data={"subscriptions": [{"subscription_id": 12345678}]},
            expected_code=400,
        )

    def test_bind_sku_unauthorized(self):
        # bind a sku that user does not own
        self.login(SUBSCRIPTION_USER)
        self.postResponse(
            resource_name=OrganizationRhSku,
            params=dict(orgname=SUBSCRIPTION_ORG),
            data={"subscriptions": [{"subscription_id": 11111}]},
            expected_code=401,
        )

    def test_remove_sku_from_org(self):
        self.login(SUBSCRIPTION_USER)
        self.postResponse(
            resource_name=OrganizationRhSku,
            params=dict(orgname=SUBSCRIPTION_ORG),
            data={"subscriptions": [{"subscription_id": 12345678}]},
            expected_code=201,
        )
        self.deleteResponse(
            resource_name=OrganizationRhSkuSubscriptionField,
            params=dict(orgname=SUBSCRIPTION_ORG, subscription_id=12345678),
            expected_code=204,
        )
        json = self.getJsonResponse(
            resource_name=OrganizationRhSku,
            params=dict(orgname=SUBSCRIPTION_ORG),
        )
        self.assertEqual(len(json), 0)

    def test_sku_stacking(self):
        # multiples of same sku
        self.login(SUBSCRIPTION_USER)
        self.postResponse(
            resource_name=OrganizationRhSku,
            params=dict(orgname=SUBSCRIPTION_ORG),
            data={"subscriptions": [{"subscription_id": 12345678}, {"subscription_id": 11223344}]},
            expected_code=201,
        )
        json = self.getJsonResponse(
            resource_name=OrganizationRhSku,
            params=dict(orgname=SUBSCRIPTION_ORG),
        )
        self.assertEqual(len(json), 2)
        json = self.getJsonResponse(OrgPrivateRepositories, params=dict(orgname=SUBSCRIPTION_ORG))
        self.assertEqual(True, json["privateAllowed"])

    def test_batch_sku_remove(self):
        self.login(SUBSCRIPTION_USER)
        self.postResponse(
            resource_name=OrganizationRhSku,
            params=dict(orgname=SUBSCRIPTION_ORG),
            data={"subscriptions": [{"subscription_id": 12345678}, {"subscription_id": 11223344}]},
            expected_code=201,
        )
        self.postResponse(
            resource_name=OrganizationRhSkuBatchRemoval,
            params=dict(orgname=SUBSCRIPTION_ORG),
            data={"subscriptions": [{"subscription_id": 12345678}, {"subscription_id": 11223344}]},
            expected_code=204,
        )
        json = self.getJsonResponse(
            resource_name=OrganizationRhSku, params=dict(orgname=SUBSCRIPTION_ORG)
        )
        self.assertEqual(len(json), 0)


class TestUserSku(ApiTestCase):
    def test_get_user_skus(self):
        self.login(SUBSCRIPTION_USER)
        json = self.getJsonResponse(UserSkuList)
        self.assertEqual(len(json), 2)


if __name__ == "__main__":
    unittest.main()