def test_delete_permission(cidc_api, clean_db, monkeypatch):
    """Check that deleting a permission works as expected."""
    gcloud_client = mock_gcloud_client(monkeypatch)
    current_user_id, other_user_id = setup_permissions(cidc_api, monkeypatch)

    with cidc_api.app_context():
        perm = Permissions.find_for_user(current_user_id)[0]

    client = cidc_api.test_client()

    # Non-admins are not allowed to delete
    gcloud_client.reset_mock()
    res = client.delete(f"permissions/{perm.id}")
    assert res.status_code == 401
    assert "not authorized to access this endpoint" in res.json["_error"][
        "message"]
    gcloud_client.grant_download_access.assert_not_called()
    gcloud_client.revoke_download_access.assert_not_called()

    make_admin(current_user_id, cidc_api)

    # Requester must supply an If-Match header
    gcloud_client.reset_mock()
    res = client.delete(f"permissions/{perm.id}")
    assert res.status_code == 428
    gcloud_client.grant_download_access.assert_not_called()
    gcloud_client.revoke_download_access.assert_not_called()

    headers = {"If-Match": "foobar"}

    # Returns NotFound if no record exists
    gcloud_client.reset_mock()
    res = client.delete(f"permissions/1232123", headers=headers)
    assert res.status_code == 404
    gcloud_client.grant_download_access.assert_not_called()
    gcloud_client.revoke_download_access.assert_not_called()

    # A mismatched ETag leads to a PreconditionFailed error
    gcloud_client.reset_mock()
    res = client.delete(f"permissions/{perm.id}", headers=headers)
    assert res.status_code == 412
    gcloud_client.grant_download_access.assert_not_called()
    gcloud_client.revoke_download_access.assert_not_called()

    headers["If-Match"] = perm._etag

    # A well-formed delete request fails if IAM revoke fails
    gcloud_client.reset_mock()
    gcloud_client.revoke_download_access.side_effect = Exception("oops")
    res = client.delete(f"permissions/{perm.id}", headers=headers)
    assert "IAM revoke failed" in res.json["_error"]["message"]
    assert res.status_code == 500
    with cidc_api.app_context():
        assert Permissions.find_by_id(perm.id) is not None
    gcloud_client.revoke_download_access.side_effect = None

    # A matching ETag leads to a successful deletion
    gcloud_client.reset_mock()
    res = client.delete(f"permissions/{perm.id}", headers=headers)
    assert res.status_code == 204
    with cidc_api.app_context():
        assert Permissions.find_by_id(perm.id) is None
    gcloud_client.grant_download_access.assert_not_called()
    gcloud_client.revoke_download_access.assert_called_once()
def test_create_permission(cidc_api, clean_db, monkeypatch):
    """Check that creating a new permission works as expected."""
    gcloud_client = mock_gcloud_client(monkeypatch)
    current_user_id, other_user_id = setup_permissions(cidc_api, monkeypatch)

    client = cidc_api.test_client()

    # Non-admins should be blocked from posting to this endpoint
    gcloud_client.reset_mocks()
    res = client.post("permissions")
    assert res.status_code == 401
    assert "not authorized to access this endpoint" in res.json["_error"][
        "message"]
    gcloud_client.grant_download_access.assert_not_called()
    gcloud_client.revoke_download_access.assert_not_called()

    make_admin(current_user_id, cidc_api)
    perm = {
        "granted_to_user": other_user_id,
        "trial_id": TRIAL_ID,
        "upload_type": "ihc",
    }

    # When an IAM grant error occurs, the permission db record shouldn't be created
    gcloud_client.reset_mocks()
    gcloud_client.grant_download_access.side_effect = Exception("oops")
    res = client.post("permissions", json=perm)
    assert "IAM grant failed" in res.json["_error"]["message"]
    assert res.status_code == 500
    with cidc_api.app_context():
        assert clean_db.query(Permissions).filter_by(**perm).all() == []
    gcloud_client.grant_download_access.side_effect = None

    # Admins can't create permissions with invalid upload types
    gcloud_client.reset_mocks()
    res = client.post("permissions", json={**perm, "upload_type": "foo"})
    assert res.status_code == 422
    assert "invalid upload type: foo" in res.json["_error"]["message"]

    # Admins should be able to create new permissions
    gcloud_client.reset_mocks()
    res = client.post("permissions", json=perm)
    assert res.status_code == 201
    assert "id" in res.json
    assert {**res.json, **perm} == res.json
    with cidc_api.app_context():
        assert Permissions.find_by_id(res.json["id"])
    gcloud_client.grant_download_access.assert_called_once()
    gcloud_client.revoke_download_access.assert_not_called()

    # Re-insertion is not allowed
    gcloud_client.reset_mocks()
    res = client.post("permissions", json=perm)
    assert res.status_code == 400
    assert "unique constraint" in res.json["_error"]["message"]
    gcloud_client.grant_download_access.assert_not_called()
    gcloud_client.revoke_download_access.assert_not_called()

    # The permission grantee must exist
    gcloud_client.reset_mocks()
    perm["granted_to_user"] = 999999999  # user doesn't exist
    res = client.post("permissions", json=perm)
    assert res.status_code == 400
    assert "user must exist, but no user found" in res.json["_error"][
        "message"]
    gcloud_client.grant_download_access.assert_not_called()
    gcloud_client.revoke_download_access.assert_not_called()

    with cidc_api.app_context():
        clean_db.query(Permissions).delete()
        clean_db.commit()

    # The permission grantee must have <= GOOGLE_MAX_DOWNLOAD_PERMISSIONS
    perm["granted_to_user"] = current_user_id
    inserts_fail_eventually = False
    upload_types = list(ALL_UPLOAD_TYPES)
    for i in range(GOOGLE_MAX_DOWNLOAD_PERMISSIONS + 1):
        gcloud_client.reset_mocks()
        perm["upload_type"] = upload_types[i]
        res = client.post("permissions", json=perm)
        if res.status_code != 201:
            assert res.status_code == 400
            assert (
                "greater than or equal to the maximum number of allowed granular permissions"
                in res.json["_error"]["message"])
            gcloud_client.grant_download_access.assert_not_called()
            gcloud_client.revoke_download_access.assert_not_called()
            inserts_fail_eventually = True
            break
    assert inserts_fail_eventually