""" Locust Testing for globalSoundRecordingSearchES and globalParticipantSearchES graphql queries """ import codecs import json import os import random import sys from os.path import abspath, dirname, join, pardir from dotenv import load_dotenv from locust import HttpUser, task, events, tag from GlobalParticipantSearchES import GlobalParticipantSearchES from GlobalParticipantSearchESxPP import GlobalParticipantSearchESxPP from GlobalSoundRecordingSearchES import GlobalSoundRecordingSearchES from utils import substr, graphql_query, generate_pdp_user_credentials, LoginInfo CATALOG_ONLY = False @events.test_start.add_listener def on_test_start(environment, **kwargs): global GP_NAMES, GSR_NAMES, HEADERS, PDP_USERS, CATALOG_ONLY # Load files with test data _location = os.path.realpath( os.path.join(os.getcwd(), os.path.dirname(__file__))) with open(os.path.join(_location, 'gp_names.txt')) as f: GP_NAMES = [name.strip() for name in f.readlines()] with open(os.path.join(_location, 'gsr_names.txt')) as f: GSR_NAMES = [name.strip() for name in f.readlines()] # uses codecs.open because neo4j desktop includes byte-order-mark (BOM) in the json export with codecs.open(os.path.join(_location, 'header_values.json'), 'r', 'utf-8-sig') as f: HEADERS = json.load(f) # Load environment variables from a .env file if present dotenv_path = abspath(join(dirname(__file__), pardir, ".env")) load_dotenv(dotenv_path) # TODO: Remove if using pdp_users, make configurable. if not os.environ.get("TOKEN"): raise RuntimeError("Please add a valid JWT to .env") PDP_USERS = None try: PDP_USERS = generate_pdp_user_credentials() for u in PDP_USERS: print(u) except Exception as e: raise RuntimeError(f"No PDP_USERS: {e}") # TODO: CATALOG_ONLY = os.environ.get("CATALOG_ONLY", "False").strip().lower() == "true" CATALOG_ONLY = True print(f"CATALOG_ONLY = {CATALOG_ONLY}") @events.test_stop.add_listener def on_test_stop(environment, **kwargs): print("Test is ending") class BaseCacheUser(HttpUser): MAX_UNIQUE_LOOKUP_TERMS = -1 """If MAX_UNIQUE_LOOKUP_TERMS is greater than 0, this value defines the total unique search terms to select from GP_NAMES and GSR_NAMES lists. There is an inverse relationship between the number of unique search terms and the likelihood of a cached GraphQL response. Smaller MAX_UNIQUE_LOOKUP_TERMS values increase the likelihood of a cached response.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) print(f"MAX_UNIQUE_LOOKUP_TERMS: {self.MAX_UNIQUE_LOOKUP_TERMS}") @tag('GlobalParticipantSearchES') @task def globalparticipants_query_by_name(self): """globalParticipantSearchES graphql query with random GP_NAMES entry.""" lookup_term = self._random_gp_names() _ = graphql_query( self, name="globalParticipantSearchES", query=GlobalParticipantSearchES, variables={ "offset": 0, "limit": 10, "term": lookup_term, "catalogOnly": CATALOG_ONLY }, headers=self._random_pdp_test_headers() ) @tag('GlobalParticipantSearchESxPP') @task def globalparticipants_query_by_name_pp(self): """globalParticipantSearchES graphql query with random GP_NAMES entry.""" lookup_term = self._random_gp_names() _ = graphql_query( self, name="globalParticipantSearchESxPP", query=GlobalParticipantSearchESxPP, variables={ "offset": 0, "limit": 10, "term": lookup_term, "catalogOnly": CATALOG_ONLY }, headers=self._random_pdp_test_headers() ) @tag('GlobalSoundRecordingSearchES') @task def globalsoundecordings_get_by_name(self): """globalSoundRecordingSearchES graphql query with random GSR_NAMES entry.""" lookup_term = self._random_gsr_names() _ = graphql_query( self, name="globalSoundRecordingSearchES", query=GlobalSoundRecordingSearchES, variables={ "offset": 0, "limit": 10, "term": lookup_term, "catalogOnly": CATALOG_ONLY }, headers=self._random_pdp_test_headers() ) def _random_gp_names(self): """ Select a random GlobalParticipant search term from GP_NAMES. MAX_UNIQUE_LOOKUP_TERMS limits the total searchable terms. :return: Search term found in GP_NAMES """ if not GP_NAMES: sys.exit(1) if self.MAX_UNIQUE_LOOKUP_TERMS > 0: return random.choice(GP_NAMES[:self.MAX_UNIQUE_LOOKUP_TERMS]) else: return random.choice(GP_NAMES) def _random_gsr_names(self): """ Select a random GlobalSoundRecording search term from GSR_NAMES. MAX_UNIQUE_LOOKUP_TERMS limits the total searchable terms. :return: Search term found in GSR_NAMES """ if not GSR_NAMES: sys.exit(1) if self.MAX_UNIQUE_LOOKUP_TERMS > 0: return random.choice(GSR_NAMES[:self.MAX_UNIQUE_LOOKUP_TERMS]) else: return random.choice(GSR_NAMES) def _random_headers(self): """ This is deprecated but keeping in case want to perf test more non-PP graphql endpoints. Populate header values from `header_values.json` """ if not HEADERS: sys.exit(1) user = random.choice(HEADERS) return { "Apollographql-Client-Name": "locust-test", "Orchard-Identity-Id": user["identity_uuid"], "Orchard-profile-type": user["profile_type"], "Orchard-Profile-Id": user["profile_uuid"], "Orchard-Identity-Uuid": user["identity_uuid"], "Content-Type": "application/json", "Authorization": os.environ["TOKEN"] } def _random_pdp_test_headers(self): if not PDP_USERS: sys.exit(1) user: LoginInfo = random.choice(PDP_USERS) if not user.profile_header_values: print(f"User {user.username} is missing profile header values") sys.exit(1) header_values = random.choice(user.profile_header_values) return { "Apollographql-Client-Name": "locust-test", "Orchard-Identity-Id": user.identity_uuid, "Orchard-profile-type": header_values["profile_type"], "Orchard-Profile-Id": header_values["profile_uuid"], "Orchard-Identity-Uuid": user.identity_uuid, "Content-Type": "application/json", "Authorization": user.token, "User-Agent": "locust-tests/PDP" } class SimpleTestUser(HttpUser): @task def globalparticipants_query_by_name(self): """globalParticipantSearchES graphql query with random GP_NAMES entry.""" lookup_term = "sevendust" _ = graphql_query( self, name="globalParticipantSearchES", query=GlobalParticipantSearchES, variables={ "offset": 0, "limit": 10, "term": lookup_term, "catalogOnly": False }, headers=self._random_headers() ) @task def globalsoundecordings_get_by_name(self): """globalSoundRecordingSearchES graphql query with random GSR_NAMES entry.""" lookup_term = "too close to hate" _ = graphql_query( self, name="globalSoundRecordingSearchES", query=GlobalSoundRecordingSearchES, variables={ "offset": 0, "limit": 10, "term": lookup_term, "catalogOnly": False }, headers=self._random_headers() ) def _random_headers(self): return { "Apollographql-Client-Name": "locust-test", "Orchard-Identity-Id": "02f3fd86-17e6-4555-8f4e-86d7b747cace", "Orchard-profile-type": "LabelProfile", "Orchard-Profile-Id": "53200d8a-1311-42b0-98ad-8b995fac127c", "Orchard-Identity-Uuid": "02f3fd86-17e6-4555-8f4e-86d7b747cace", "Content-Type": "application/json", "Authorization": os.environ["TOKEN"] } class HeavyCacheUser(BaseCacheUser): """ User that only searches 10 unique Participants and SoundRecordings for mostly cached lookups. """ weight = 5 MAX_UNIQUE_LOOKUP_TERMS = 10 class MediumCacheUser(BaseCacheUser): """ User that only searches 1000 unique Participants and SoundRecordings for some cached lookups. """ weight = 5 MAX_UNIQUE_LOOKUP_TERMS = 1000 class NoCacheUser(BaseCacheUser): """ User that searches mostly uncached Participant and SoundRecording terms. """ weight = 5 def _random_gp_names(self): """ Returns a random substring from the GlobalParticipant search term returned by the base class method. Decreases the probability querying a single GlobalParticipant more than once in a test run. :return: Substring of term found in GP_NAMES """ lookup_term = super()._random_gp_names() return substr(lookup_term) def _random_gsr_names(self): """ Returns a random substring from the GlobalSoundRecording search term returned by the base class method. Decreases the probability querying a single GlobalSoundRecording more than once in a test run. :return: Substring of term found in GSR_NAMES """ lookup_term = super()._random_gsr_names() return substr(lookup_term)