Skip to content

Commit 0c98d1f

Browse files
authored
Vb/support test ephemeral sdk 241 (#1232)
2 parents 388d062 + 82e03c0 commit 0c98d1f

File tree

5 files changed

+274
-80
lines changed

5 files changed

+274
-80
lines changed

Makefile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,17 @@ test-custom: build-image
5353
-e LABELBOX_TEST_GRAPHQL_API_ENDPOINT=${LABELBOX_TEST_GRAPHQL_API_ENDPOINT} \
5454
-e LABELBOX_TEST_REST_API_ENDPOINT=${LABELBOX_TEST_REST_API_ENDPOINT} \
5555
local/labelbox-python:test pytest $(PATH_TO_TEST)
56+
57+
test-ephemeral: build-image
58+
59+
@# if PATH_TO_TEST we assume you know what you are doing
60+
@if [ -z ${PATH_TO_TEST} ]; then \
61+
./scripts/ensure_local_setup.sh; \
62+
fi
63+
64+
docker run -it --rm -v ${PWD}:/usr/src -w /usr/src \
65+
-e LABELBOX_TEST_ENVIRON="ephemeral" \
66+
-e DA_GCP_LABELBOX_API_KEY=${DA_GCP_LABELBOX_API_KEY} \
67+
-e SERVICE_API_KEY=${SERVICE_API_KEY} \
68+
-e LABELBOX_TEST_BASE_URL="http://host.docker.internal:8080" \
69+
local/labelbox-python:test pytest $(PATH_TO_TEST)

tests/integration/conftest.py

Lines changed: 7 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,34 @@
22
from itertools import islice
33
import json
44
import os
5-
import re
65
import sys
76
import time
87
import uuid
9-
from enum import Enum
108
from types import SimpleNamespace
119
from typing import Type, List
1210

1311
import pytest
1412
import requests
1513

16-
from labelbox import Client, Dataset
14+
from labelbox import Dataset
1715
from labelbox import LabelingFrontend
1816
from labelbox import OntologyBuilder, Tool, Option, Classification, MediaType
1917
from labelbox.orm import query
2018
from labelbox.pagination import PaginatedCollection
2119
from labelbox.schema.annotation_import import LabelImport
2220
from labelbox.schema.enums import AnnotationImportState
2321
from labelbox.schema.invite import Invite
24-
from labelbox.schema.project import Project
2522
from labelbox.schema.quality_mode import QualityMode
2623
from labelbox.schema.queue_mode import QueueMode
2724
from labelbox.schema.user import User
25+
from support.integration_client import Environ, IntegrationClient, EphemeralClient, AdminClient
2826

2927
IMG_URL = "https://picsum.photos/200/300.jpg"
3028
SMALL_DATASET_URL = "https://storage.googleapis.com/lb-artifacts-testing-public/sdk_integration_test/potato.jpeg"
3129
DATA_ROW_PROCESSING_WAIT_TIMEOUT_SECONDS = 30
3230
DATA_ROW_PROCESSING_WAIT_SLEEP_INTERNAL_SECONDS = 3
3331

3432

35-
class Environ(Enum):
36-
LOCAL = 'local'
37-
PROD = 'prod'
38-
STAGING = 'staging'
39-
ONPREM = 'onprem'
40-
CUSTOM = 'custom'
41-
STAGING_EU = 'staging-eu'
42-
43-
4433
@pytest.fixture(scope="session")
4534
def environ() -> Environ:
4635
"""
@@ -56,56 +45,6 @@ def environ() -> Environ:
5645
raise Exception(f'Missing LABELBOX_TEST_ENVIRON in: {os.environ}')
5746

5847

59-
def graphql_url(environ: str) -> str:
60-
if environ == Environ.PROD:
61-
return 'https://api.labelbox.com/graphql'
62-
elif environ == Environ.STAGING:
63-
return 'https://api.lb-stage.xyz/graphql'
64-
elif environ == Environ.STAGING_EU:
65-
return 'https://api.eu-de.lb-stage.xyz/graphql'
66-
elif environ == Environ.ONPREM:
67-
hostname = os.environ.get('LABELBOX_TEST_ONPREM_HOSTNAME', None)
68-
if hostname is None:
69-
raise Exception(f"Missing LABELBOX_TEST_ONPREM_INSTANCE")
70-
return f"{hostname}/api/_gql"
71-
elif environ == Environ.CUSTOM:
72-
graphql_api_endpoint = os.environ.get(
73-
'LABELBOX_TEST_GRAPHQL_API_ENDPOINT')
74-
if graphql_api_endpoint is None:
75-
raise Exception(f"Missing LABELBOX_TEST_GRAPHQL_API_ENDPOINT")
76-
return graphql_api_endpoint
77-
return 'http://host.docker.internal:8080/graphql'
78-
79-
80-
def rest_url(environ: str) -> str:
81-
if environ == Environ.PROD:
82-
return 'https://api.labelbox.com/api/v1'
83-
elif environ == Environ.STAGING:
84-
return 'https://api.lb-stage.xyz/api/v1'
85-
elif environ == Environ.STAGING_EU:
86-
return 'https://api.eu-de.lb-stage.xyz/api/v1'
87-
elif environ == Environ.CUSTOM:
88-
rest_api_endpoint = os.environ.get('LABELBOX_TEST_REST_API_ENDPOINT')
89-
if rest_api_endpoint is None:
90-
raise Exception(f"Missing LABELBOX_TEST_REST_API_ENDPOINT")
91-
return rest_api_endpoint
92-
return 'http://host.docker.internal:8080/api/v1'
93-
94-
95-
def testing_api_key(environ: str) -> str:
96-
if environ == Environ.PROD:
97-
return os.environ["LABELBOX_TEST_API_KEY_PROD"]
98-
elif environ == Environ.STAGING:
99-
return os.environ["LABELBOX_TEST_API_KEY_STAGING"]
100-
elif environ == Environ.STAGING_EU:
101-
return os.environ["LABELBOX_TEST_API_KEY_STAGING_EU"]
102-
elif environ == Environ.ONPREM:
103-
return os.environ["LABELBOX_TEST_API_KEY_ONPREM"]
104-
elif environ == Environ.CUSTOM:
105-
return os.environ["LABELBOX_TEST_API_KEY_CUSTOM"]
106-
return os.environ["LABELBOX_TEST_API_KEY_LOCAL"]
107-
108-
10948
def cancel_invite(client, invite_id):
11049
"""
11150
Do not use. Only for testing.
@@ -155,27 +94,15 @@ def queries():
15594
get_invites=get_invites)
15695

15796

158-
class IntegrationClient(Client):
159-
160-
def __init__(self, environ: str) -> None:
161-
api_url = graphql_url(environ)
162-
api_key = testing_api_key(environ)
163-
rest_endpoint = rest_url(environ)
164-
super().__init__(api_key,
165-
api_url,
166-
enable_experimental=True,
167-
rest_endpoint=rest_endpoint)
168-
self.queries = []
169-
170-
def execute(self, query=None, params=None, check_naming=True, **kwargs):
171-
if check_naming and query is not None:
172-
assert re.match(r"(?:query|mutation) \w+PyApi", query) is not None
173-
self.queries.append((query, params))
174-
return super().execute(query, params, **kwargs)
97+
@pytest.fixture(scope="session")
98+
def admin_client(environ: str):
99+
return AdminClient(environ)
175100

176101

177102
@pytest.fixture(scope="session")
178103
def client(environ: str):
104+
if environ == Environ.EPHEMERAL:
105+
return EphemeralClient()
179106
return IntegrationClient(environ)
180107

181108

tests/integration/support/__init__.py

Whitespace-only changes.
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
import os
2+
import re
3+
import uuid
4+
from enum import Enum
5+
from typing import Tuple
6+
7+
import requests
8+
9+
from labelbox import Client
10+
11+
EPHEMERAL_BASE_URL = "http://lb-api-public"
12+
13+
14+
class Environ(Enum):
15+
LOCAL = 'local'
16+
PROD = 'prod'
17+
STAGING = 'staging'
18+
ONPREM = 'onprem'
19+
CUSTOM = 'custom'
20+
STAGING_EU = 'staging-eu'
21+
EPHEMERAL = 'ephemeral' # Used for testing PRs with ephemeral environments
22+
23+
24+
def ephemeral_endpoint() -> str:
25+
return os.getenv('LABELBOX_TEST_BASE_URL', EPHEMERAL_BASE_URL)
26+
27+
28+
def graphql_url(environ: str) -> str:
29+
if environ == Environ.PROD:
30+
return 'https://api.labelbox.com/graphql'
31+
elif environ == Environ.STAGING:
32+
return 'https://api.lb-stage.xyz/graphql'
33+
elif environ == Environ.STAGING_EU:
34+
return 'https://api.eu-de.lb-stage.xyz/graphql'
35+
elif environ == Environ.ONPREM:
36+
hostname = os.environ.get('LABELBOX_TEST_ONPREM_HOSTNAME', None)
37+
if hostname is None:
38+
raise Exception(f"Missing LABELBOX_TEST_ONPREM_INSTANCE")
39+
return f"{hostname}/api/_gql"
40+
elif environ == Environ.CUSTOM:
41+
graphql_api_endpoint = os.environ.get(
42+
'LABELBOX_TEST_GRAPHQL_API_ENDPOINT')
43+
if graphql_api_endpoint is None:
44+
raise Exception(f"Missing LABELBOX_TEST_GRAPHQL_API_ENDPOINT")
45+
return graphql_api_endpoint
46+
elif environ == Environ.EPHEMERAL:
47+
return f"{ephemeral_endpoint()}/graphql"
48+
return 'http://host.docker.internal:8080/graphql'
49+
50+
51+
def rest_url(environ: str) -> str:
52+
if environ == Environ.PROD:
53+
return 'https://api.labelbox.com/api/v1'
54+
elif environ == Environ.STAGING:
55+
return 'https://api.lb-stage.xyz/api/v1'
56+
elif environ == Environ.STAGING_EU:
57+
return 'https://api.eu-de.lb-stage.xyz/api/v1'
58+
elif environ == Environ.CUSTOM:
59+
rest_api_endpoint = os.environ.get('LABELBOX_TEST_REST_API_ENDPOINT')
60+
if rest_api_endpoint is None:
61+
raise Exception(f"Missing LABELBOX_TEST_REST_API_ENDPOINT")
62+
return rest_api_endpoint
63+
elif environ == Environ.EPHEMERAL:
64+
return f"{ephemeral_endpoint()}/api/v1"
65+
return 'http://host.docker.internal:8080/api/v1'
66+
67+
68+
def testing_api_key(environ: str) -> str:
69+
if environ == Environ.PROD:
70+
return os.environ["LABELBOX_TEST_API_KEY_PROD"]
71+
elif environ == Environ.STAGING:
72+
return os.environ["LABELBOX_TEST_API_KEY_STAGING"]
73+
elif environ == Environ.STAGING_EU:
74+
return os.environ["LABELBOX_TEST_API_KEY_STAGING_EU"]
75+
elif environ == Environ.ONPREM:
76+
return os.environ["LABELBOX_TEST_API_KEY_ONPREM"]
77+
elif environ == Environ.CUSTOM:
78+
return os.environ["LABELBOX_TEST_API_KEY_CUSTOM"]
79+
return os.environ["LABELBOX_TEST_API_KEY_LOCAL"]
80+
81+
82+
def service_api_key() -> str:
83+
return os.environ["SERVICE_API_KEY"]
84+
85+
86+
class IntegrationClient(Client):
87+
88+
def __init__(self, environ: str) -> None:
89+
api_url = graphql_url(environ)
90+
api_key = testing_api_key(environ)
91+
rest_endpoint = rest_url(environ)
92+
93+
super().__init__(api_key,
94+
api_url,
95+
enable_experimental=True,
96+
rest_endpoint=rest_endpoint)
97+
self.queries = []
98+
99+
def execute(self, query=None, params=None, check_naming=True, **kwargs):
100+
if check_naming and query is not None:
101+
assert re.match(r"(?:query|mutation) \w+PyApi", query) is not None
102+
self.queries.append((query, params))
103+
return super().execute(query, params, **kwargs)
104+
105+
106+
class AdminClient(Client):
107+
108+
def __init__(self, env):
109+
"""
110+
The admin client creates organizations and users using admin api described here https://labelbox.atlassian.net/wiki/spaces/AP/pages/2206564433/Internal+Admin+APIs.
111+
"""
112+
self._api_key = service_api_key()
113+
self._admin_endpoint = f"{ephemeral_endpoint()}/admin/v1"
114+
self._api_url = graphql_url(env)
115+
self._rest_endpoint = rest_url(env)
116+
117+
super().__init__(self._api_key,
118+
self._api_url,
119+
enable_experimental=True,
120+
rest_endpoint=self._rest_endpoint)
121+
122+
def _create_organization(self) -> str:
123+
endpoint = f"{self._admin_endpoint}/organizations/"
124+
response = requests.post(
125+
endpoint,
126+
headers=self.headers,
127+
json={"name": f"Test Org {uuid.uuid4()}"},
128+
)
129+
130+
data = response.json()
131+
if response.status_code not in [
132+
requests.codes.created, requests.codes.ok
133+
]:
134+
raise Exception("Failed to create org, message: " +
135+
str(data['message']))
136+
137+
return data['id']
138+
139+
def _create_user(self, organization_id=None) -> Tuple[str, str]:
140+
if organization_id is None:
141+
organization_id = self.organization_id
142+
143+
endpoint = f"{self._admin_endpoint}/user-identities/"
144+
identity_id = f"e2e+{uuid.uuid4()}"
145+
146+
response = requests.post(
147+
endpoint,
148+
headers=self.headers,
149+
json={
150+
"identityId": identity_id,
151+
"email": "email@email.com",
152+
"name": f"tester{uuid.uuid4()}",
153+
"verificationStatus": "VERIFIED",
154+
},
155+
)
156+
data = response.json()
157+
if response.status_code not in [
158+
requests.codes.created, requests.codes.ok
159+
]:
160+
raise Exception("Failed to create user, message: " +
161+
str(data['message']))
162+
163+
user_identity_id = data['identityId']
164+
165+
endpoint = f"{self._admin_endpoint}/organizations/{organization_id}/users/"
166+
response = requests.post(
167+
endpoint,
168+
headers=self.headers,
169+
json={
170+
"identityId": user_identity_id,
171+
"organizationRole": "Admin"
172+
},
173+
)
174+
175+
data = response.json()
176+
if response.status_code not in [
177+
requests.codes.created, requests.codes.ok
178+
]:
179+
raise Exception("Failed to create link user to org, message: " +
180+
str(data['message']))
181+
182+
user_id = data['id']
183+
184+
endpoint = f"{self._admin_endpoint}/users/{user_id}/token"
185+
response = requests.get(
186+
endpoint,
187+
headers=self.headers,
188+
)
189+
data = response.json()
190+
if response.status_code not in [
191+
requests.codes.created, requests.codes.ok
192+
]:
193+
raise Exception("Failed to create ephemeral user, message: " +
194+
str(data['message']))
195+
196+
token = data["token"]
197+
198+
return user_id, token
199+
200+
def create_api_key_for_user(self) -> str:
201+
organization_id = self._create_organization()
202+
_, user_token = self._create_user(organization_id)
203+
key_name = f"test-key+{uuid.uuid4()}"
204+
query = """mutation CreateApiKeyPyApi($name: String!) {
205+
createApiKey(data: {name: $name}) {
206+
id
207+
jwt
208+
}
209+
}
210+
"""
211+
params = {"name": key_name}
212+
self.headers["Authorization"] = f"Bearer {user_token}"
213+
res = self.execute(query, params, error_log_key="errors")
214+
215+
return res["createApiKey"]["jwt"]
216+
217+
218+
class EphemeralClient(Client):
219+
220+
def __init__(self, environ=Environ.EPHEMERAL):
221+
self.admin_client = AdminClient(environ)
222+
self.api_key = self.admin_client.create_api_key_for_user()
223+
api_url = graphql_url(environ)
224+
rest_endpoint = rest_url(environ)
225+
226+
super().__init__(self.api_key,
227+
api_url,
228+
enable_experimental=True,
229+
rest_endpoint=rest_endpoint)

0 commit comments

Comments
 (0)