diff --git a/.gitignore b/.gitignore
index cea554d..780906a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,3 +38,7 @@ docker/ubkg-api/BUILD
 BUILD
 
 **/__pycache__
+
+/tests/*/*.out
+/src/cells_index/*.csv
+/src/cells_index/*.tsv
\ No newline at end of file
diff --git a/README.md b/README.md
index a64d227..ecc3a5a 100644
--- a/README.md
+++ b/README.md
@@ -55,13 +55,17 @@ If you are modifying code only in hs-ontology-api, you will only need
 to use the PyPy package version of ubkg-api. The package is included in the requirements.txt file of this repo.
 
 If you need to modify both the hs-ontology-api and ubkg-api in concert, you will
-need to work with a local instance of the ubkg-api. This is possible by doing the following:
-1. Check out a branch of ubkg-api.
-2. Configure the local branch of ubkg-api, similarly to the local instance of hs-ontology-api.
-3. Start the local instance of ubkg-api.
-4. In the virtual environment for hs-ontology-api, install the local instance of ubkg-api using pip with the **-e** flag. This will override the pointer to the ubkg-api package. 
-
-``pip install -e path/to/local/ubkg/repo``
+need to work with a local or branch instance of the ubkg-api. This is possible by doing the following:
+1. If your working ubkg-api instance has been committed to a branch, you can point to the branch instance in requirements.txt with a command such as ``git+https://github.com/x-atlas-consortia/ubkg-api.git@<YOUR BRANCH>``
+2. Check out a branch of ubkg-api.
+2. Configure the app.cfg file of the local branch of ubkg-api to connect to the appropriate UBKG instance. 
+3. In the virtual environment for hs-ontology-api, install an editable local instance of ubkg-api. Two ways to do this:
+   a. ``pip install -e path/to/local/ubkg-api/repo``
+   b. If using PyCharm, in the **Python Packages** tab,
+      1) Click **Add Package**.
+      2) Navigate to the root of the ubkg-api repo.
+      3) Indicate that the package is editable.
+4. Because ubkg-api has a PyPI TOML file, any of the aforementioned commands will compile a local package and override the pointer to the ubkg-api package.
 
 ## Connecting to the local instance of hs-ontology-api
 For URLs that execute endpoints in your local instance, use the values indicated in the **main.py** script, in the section prefaced with the comment `For local development/testing`:
diff --git a/VERSION b/VERSION
index 079d7f6..359a5b9 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.4.11
+2.0.0
\ No newline at end of file
diff --git a/hs-ontology-api-spec.yaml b/hs-ontology-api-spec.yaml
index 91cb4f6..1bea432 100644
--- a/hs-ontology-api-spec.yaml
+++ b/hs-ontology-api-spec.yaml
@@ -2,7 +2,7 @@ openapi: 3.0.3
 info:
   title: HubMAP/SenNet Ontology API (hs-ontology-api)
   description: The HuBMAP/SenNet Ontology API contains endpoints for querying a [UBKG](https://ubkg.docs.xconsortia.org/) instance with content from the [HuBMAP/SenNet context](https://ubkg.docs.xconsortia.org/contexts/#hubmapsennet-context). The hs-ontology-api imports the [ubkg-api](https://smart-api.info/ui/96e5b5c0b0efeef5b93ea98ac2794837), which encapsulates both basic connectivity to a UBKG instance and generic endpoint code.
-  version: 1.4.11
+  version: 2.0.0
   contact:
     name: GitHub repository
     url: https://github.com/x-atlas-consortia/hs-ontology-api
@@ -1894,4 +1894,4 @@ components:
               schema:
                 type: string
                 description: name of schema
-                example: imc3d
\ No newline at end of file
+                example: imc3d
diff --git a/src/hs_ontology_api/cypher/celltypedetail.cypher b/src/hs_ontology_api/cypher/celltypedetail.cypher
index 765528e..b9ed0a8 100644
--- a/src/hs_ontology_api/cypher/celltypedetail.cypher
+++ b/src/hs_ontology_api/cypher/celltypedetail.cypher
@@ -12,8 +12,9 @@ CALL
 // The calling function in neo4j_logic.py will replace $ids.
 WITH [$ids] AS ids
 
-OPTIONAL MATCH (pCL:Concept)-[:CODE]->(cCL:Code) WHERE cCL.SAB='CL' AND CASE WHEN ids[0]<>'' THEN ANY(id in ids WHERE cCL.CODE=id) ELSE 1=1 END RETURN DISTINCT pCL.CUI AS CLCUI
-}
+// APRIL 2024 Bug fix to use CodeID instead of CODE for cases of leading zeroes in strings.
+OPTIONAL MATCH (pCL:Concept)-[:CODE]->(cCL:Code)
+WHERE CASE WHEN ids[0]<>'' THEN ANY(id in ids WHERE cCL.CodeID='CL:'+id) ELSE 1=1 END RETURN DISTINCT pCL.CUI AS CLCUI}
 
 CALL
 {
@@ -54,13 +55,16 @@ ORDER BY CLID
 UNION
 
 //CL-HGNC mappings via HRA
+// APRIL 2024 - HRA changed "has_marker_component" to "characterized_by"
 
 //HGNC ID
 WITH CLCUI
-OPTIONAL MATCH (cCL:Code)<-[:CODE]-(pCL:Concept)-[:has_marker_component]->(pGene:Concept)-[:CODE]->(cGene:Code)-[r]->(tGene:Term)
+OPTIONAL MATCH (cCL:Code)<-[:CODE]-(pCL:Concept)-[:characterized_by]->(pGene:Concept)-[:CODE]->(cGene:Code)-[r]->(tGene:Term)
 WHERE pCL.CUI=CLCUI AND cGene.SAB='HGNC' AND r.CUI=pGene.CUI AND cCL.SAB='CL' AND type(r) IN ['ACR','PT']
-RETURN distinct cCL.CodeID as CLID, 'cell_types_genes' as ret_key, cGene.CodeID + '|' + apoc.text.join(COLLECT(tGene.name),'|') AS ret_value
-ORDER BY CLID, cGene.CodeID + '|' + apoc.text.join(COLLECT(tGene.name),'|')
+WITH COLLECT(tGene.name) AS tgene_names, cGene.CodeID AS cgene_codeid, cCL.CodeID AS ccl_codeid
+WITH distinct ccl_codeid AS CLID, 'cell_types_genes' AS ret_key, cgene_codeid+'|'+apoc.text.join(tgene_names,'|') AS ret_value
+RETURN CLID, ret_key, ret_value
+ORDER BY CLID, ret_value
 
 UNION
 
@@ -110,4 +114,4 @@ map['cell_types_definition'] AS cell_types_definition,
 map['cell_types_genes'] AS cell_types_genes,
 map['cell_types_organ'] AS cell_types_organs
 
-order by CLID
\ No newline at end of file
+order by CLID
diff --git a/src/hs_ontology_api/cypher/genedetail.cypher b/src/hs_ontology_api/cypher/genedetail.cypher
index e25e6f3..d45fc4b 100644
--- a/src/hs_ontology_api/cypher/genedetail.cypher
+++ b/src/hs_ontology_api/cypher/genedetail.cypher
@@ -74,8 +74,9 @@ ORDER BY hgnc_id,ret_key
 UNION
 
 //Cell types - CL Codes
+// APRIL 2024 - HRA changed "has_marker_component" to "characterized_by"
 WITH GeneCUI
-OPTIONAL MATCH (cGene:Code)<-[:CODE]-(pGene:Concept)-[:inverse_has_marker_component]->(pCL:Concept)-[:CODE]->(cCL:Code)-[rCL]->(tCL:Term) WHERE pGene.CUI=GeneCUI AND cGene.SAB='HGNC' AND cCL.SAB='CL' AND rCL.CUI=pCL.CUI  RETURN toInteger(cGene.CODE) AS hgnc_id, 'cell_types_code' AS  ret_key, cCL.CodeID AS ret_value
+OPTIONAL MATCH (cGene:Code)<-[:CODE]-(pGene:Concept)-[:inverse_characterized_by]->(pCL:Concept)-[:CODE]->(cCL:Code)-[rCL]->(tCL:Term) WHERE pGene.CUI=GeneCUI AND cGene.SAB='HGNC' AND cCL.SAB='CL' AND rCL.CUI=pCL.CUI  RETURN toInteger(cGene.CODE) AS hgnc_id, 'cell_types_code' AS  ret_key, cCL.CodeID AS ret_value
 ORDER BY  hgnc_id,ret_key,ret_value
 
 UNION
@@ -87,27 +88,34 @@ UNION
 // The preferred term will be the term of type PT; if there is no PT, then any of the others of type PT_SAB will do.
 
 // First, order the preferred terms by whether they are the PT or a PT_SAB.
+// APRIL 2024 - HRA changed the label from "has_marker_component" to "characterized_by"
 WITH GeneCUI
 CALL{
 WITH GeneCUI
-OPTIONAL MATCH (cGene:Code)<-[:CODE]-(pGene:Concept)-[:inverse_has_marker_component]->(pCL:Concept)-[:CODE]->(cCL:Code)-[rCL]->(tCL:Term) WHERE pGene.CUI=GeneCUI AND cGene.SAB='HGNC' AND cCL.SAB='CL' AND rCL.CUI=pCL.CUI AND type(rCL) STARTS WITH 'PT' RETURN toInteger(cGene.CODE) AS hgnc_id, cCL.CodeID AS CLID, MIN(CASE WHEN type(rCL)='PT' THEN 0 ELSE 1 END) AS mintype order by hgnc_id,CLID,mintype
+OPTIONAL MATCH (cGene:Code)<-[:CODE]-(pGene:Concept)-[:inverse_characterized_by]->(pCL:Concept)-[:CODE]->(cCL:Code)-[rCL]->(tCL:Term) WHERE pGene.CUI=GeneCUI AND cGene.SAB='HGNC' AND cCL.SAB='CL' AND rCL.CUI=pCL.CUI AND type(rCL) STARTS WITH 'PT' RETURN toInteger(cGene.CODE) AS hgnc_id, cCL.CodeID AS CLID, MIN(CASE WHEN type(rCL)='PT' THEN 0 ELSE 1 END) AS mintype order by hgnc_id,CLID,mintype
 }
 
 // Next, filter to either the PT or one of the PT_SABs.
+// MARCH 2024 - WITH used in return to upgrade to v5 Cypher.
 WITH hgnc_id, CLID, mintype
 OPTIONAL MATCH (cCL:Code)-[rCL]->(tCL:Term)
 where cCL.CodeID = CLID AND type(rCL) STARTS WITH 'PT'
 AND CASE WHEN type(rCL)='PT' THEN 0 ELSE 1 END=mintype
-return  hgnc_id, 'cell_types_name' AS ret_key, CLID +'|'+ CASE WHEN tCL.name IS NULL THEN '' ELSE tCL.name END AS ret_value
+WITH hgnc_id, 'cell_types_name' AS ret_key, CLID +'|'+ CASE WHEN tCL.name IS NULL THEN '' ELSE tCL.name END AS ret_value
+RETURN hgnc_id, ret_key, ret_value
 
 UNION
 
 // Cell types - CL code|definition
 // Definitions link to Concepts and multiple CL codes can match to the same concept; however, each CL code has a "preferred" CUI, identified by the CUI property of the relationship of any of the code's linked terms.
 
+// MARCH 2024 - final WITH added to work with v5 Cypher
+// APRIL 2024 - HRA changed "has_marker_component" to "characterized_by"
 WITH GeneCUI
-OPTIONAL MATCH (cGene:Code)<-[:CODE]-(pGene:Concept)-[:inverse_has_marker_component]->(pCL:Concept)-[:CODE]->(cCL:Code)-[rCL]->(tCL:Term),(pCL:Concept)-[:DEF]->(dCL:Definition) WHERE rCL.CUI=pCL.CUI AND pGene.CUI=GeneCUI AND cGene.SAB='HGNC' AND cCL.SAB='CL' AND dCL.SAB='CL' RETURN DISTINCT toInteger(cGene.CODE) AS hgnc_id,'cell_types_definition' as ret_key, cCL.CodeID + '|'+ dCL.DEF as ret_value
-ORDER BY hgnc_id,cCL.CodeID + '|'+ dCL.DEF
+OPTIONAL MATCH (cGene:Code)<-[:CODE]-(pGene:Concept)-[:inverse_characterized_by]->(pCL:Concept)-[:CODE]->(cCL:Code)-[rCL]->(tCL:Term),(pCL:Concept)-[:DEF]->(dCL:Definition) WHERE rCL.CUI=pCL.CUI AND pGene.CUI=GeneCUI AND cGene.SAB='HGNC' AND cCL.SAB='CL' AND dCL.SAB='CL'
+WITH toInteger(cGene.CODE) AS hgnc_id,'cell_types_definition' as ret_key, cCL.CodeID + '|'+ dCL.DEF as ret_value
+RETURN DISTINCT hgnc_id, ret_key, ret_value
+ORDER BY hgnc_id, ret_value
 
 UNION
 
@@ -118,32 +126,36 @@ UNION
 // 3. Assigns UBERON codes as cross-references to AZ organ codes.
 //
 // To get organ information, map gene to cell type to organ location.
+// APRIL 2024 - HRA changed "has_marker_component" to "characterized_by"
 WITH GeneCUI
 //First, get Azimuth Codes that are cross-referenced to CL codes. For the case of a CL code being cross-referenced to multiple AZ codes, only one AZ code gets the "preferred" cross-reference to the CL code; however, all AZ codes have a cross-reference to the CL code, so do not check on rAZ.CUI=pCL.CUI.
 CALL
 {WITH GeneCUI
-OPTIONAL MATCH (cGene:Code)<-[:CODE]-(pGene:Concept)-[:inverse_has_marker_component]->(pCL:Concept)-[:CODE]->(cCL:Code)-[rCL]->(tCL:Term), (pCL:Concept)-[:CODE]->(cAZ:Code)-[rAZ]->(tAZ:Term) WHERE rCL.CUI=pCL.CUI AND pGene.CUI=GeneCUI AND cGene.SAB='HGNC' AND cCL.SAB='CL' AND cAZ.SAB='AZ' RETURN DISTINCT toInteger(cGene.CODE) AS hgnc_id,cCL.CodeID as CLID,cAZ.CodeID AS AZID}
+OPTIONAL MATCH (cGene:Code)<-[:CODE]-(pGene:Concept)-[:inverse_characterized_by]->(pCL:Concept)-[:CODE]->(cCL:Code)-[rCL]->(tCL:Term), (pCL:Concept)-[:CODE]->(cAZ:Code)-[rAZ]->(tAZ:Term) WHERE rCL.CUI=pCL.CUI AND pGene.CUI=GeneCUI AND cGene.SAB='HGNC' AND cCL.SAB='CL' AND cAZ.SAB='AZ' RETURN DISTINCT toInteger(cGene.CODE) AS hgnc_id,cCL.CodeID as CLID,cAZ.CodeID AS AZID}
 //Use the AZ codes to map to concepts that have located_in relationships with AZ organ codes. The AZ organ codes are cross-referenced to UBERON codes. Limit the located_in relationships to those from AZ.
 CALL
 {WITH AZID
 OPTIONAL MATCH (cAZ:Code)<-[:CODE]-(pAZ:Concept)-[rAZUB:located_in]->(pUB:Concept)-[:CODE]->(cUB:Code)-[rUB:PT]->(tUB:Term) WHERE rAZUB.SAB='AZ' AND rUB.CUI=pUB.CUI AND cAZ.CodeID=AZID AND cUB.SAB='UBERON' RETURN cUB.CodeID+'*'+ tUB.name + '' as UBERONID
 }
 
-WITH hgnc_id, CLID,UBERONID
-RETURN DISTINCT hgnc_id,'cell_types_organ' as ret_key, CLID+ '|' + apoc.text.join(COLLECT(DISTINCT UBERONID),",")  AS ret_value
-ORDER BY hgnc_id, CLID+ '|' + apoc.text.join(COLLECT(DISTINCT UBERONID),",")
+WITH hgnc_id, 'cell_types_organ' as ret_key, CLID,UBERONID, CLID+ '|' + apoc.text.join(COLLECT(DISTINCT UBERONID),",") AS ret_value
+RETURN DISTINCT hgnc_id, ret_key, ret_value
+ORDER BY hgnc_id, ret_value
 
 // Indicate the source of cell type information.
+// APRIL 2024 - HRA changed "has_marker_component" to "characterized_by"
 UNION
 WITH GeneCUI
-OPTIONAL MATCH (cGene:Code)<-[:CODE]-(pGene:Concept)-[:inverse_has_marker_component]->(pCL:Concept)-[:CODE]->(cCL:Code)-[rCL]->(tCL:Term) WHERE rCL.CUI=pCL.CUI AND pGene.CUI=GeneCUI AND cGene.SAB='HGNC' AND cCL.SAB='CL' RETURN DISTINCT toInteger(cGene.CODE) AS hgnc_id,'cell_types_source' as ret_key, cCL.CodeID + '|Human Reference Atlas' as ret_value
+OPTIONAL MATCH (cGene:Code)<-[:CODE]-(pGene:Concept)-[:inverse_characterized_by]->(pCL:Concept)-[:CODE]->(cCL:Code)-[rCL]->(tCL:Term) WHERE rCL.CUI=pCL.CUI AND pGene.CUI=GeneCUI AND cGene.SAB='HGNC' AND cCL.SAB='CL' RETURN DISTINCT toInteger(cGene.CODE) AS hgnc_id,'cell_types_source' as ret_key, cCL.CodeID + '|Human Reference Atlas' as ret_value
 ORDER BY hgnc_id,cCL.CodeID + '|Human Reference Atlas'
 
 }
 
+// APRIL 2024 bug fix check for null gene before calling fromlists
+
 WITH hgnc_id, ret_key, COLLECT(ret_value) AS values
-WITH hgnc_id,apoc.map.fromLists(COLLECT(ret_key),COLLECT(values)) AS map
 WHERE hgnc_id IS NOT NULL
+WITH hgnc_id,apoc.map.fromLists(COLLECT(ret_key),COLLECT(values)) AS map
 RETURN hgnc_id,
 map['approved_symbol'] AS approved_symbol,
 map['approved_name'] AS approved_name,
@@ -159,4 +171,4 @@ map['cell_types_definition'] AS cell_types_code_definition,
 map['cell_types_organ'] AS cell_types_codes_organ,
 map['cell_types_source'] AS cell_types_codes_source
 
-order by hgnc_id
\ No newline at end of file
+order by hgnc_id
diff --git a/src/main.py b/src/main.py
index 91256cc..6670d07 100755
--- a/src/main.py
+++ b/src/main.py
@@ -38,7 +38,8 @@ def make_flask_config():
     return temp_flask_app.config
 
 
-app = UbkgAPI(make_flask_config()).app
+app = UbkgAPI(make_flask_config(), Path(__file__).absolute().parent.parent).app
+
 app.register_blueprint(assaytype_blueprint)
 app.register_blueprint(assayname_blueprint)
 app.register_blueprint(datasets_blueprint)
@@ -68,22 +69,6 @@ def make_flask_config():
 app.cells_client = OntologyCellsClient(cellsurl)
 
 
-# Defining the /status endpoint in the ubkg_api package will cause 500 error
-# Because the VERSION and BUILD files are not built into the package
-@app.route('/status', methods=['GET'])
-def api_status():
-    status_data = {
-        # Use strip() to remove leading and trailing spaces, newlines, and tabs
-        'version': (Path(__file__).absolute().parent.parent / 'VERSION').read_text().strip(),
-        'build': (Path(__file__).absolute().parent.parent / 'BUILD').read_text().strip(),
-        'neo4j_connection': False
-    }
-    is_connected = current_app.neo4jConnectionHelper.check_connection()
-    if is_connected:
-        status_data['neo4j_connection'] = True
-
-    return jsonify(status_data)
-
 ####################################################################################################
 ## For local development/testing
 ####################################################################################################
diff --git a/src/requirements.txt b/src/requirements.txt
index 2a669e4..d013d6e 100644
--- a/src/requirements.txt
+++ b/src/requirements.txt
@@ -1,6 +1,6 @@
-ubkg-api==1.4.0
-Flask == 2.1.3
-neo4j == 4.4
+ubkg-api==2.1.1
+Flask==2.1.3
+neo4j==5.15.0
 
 # for analysis of tabular data
 pandas==1.5.0
@@ -12,4 +12,9 @@ numpy==1.23.5
 Werkzeug==2.3.7
 
 # Cells API client
-hubmap-api-py-client==0.0.9
\ No newline at end of file
+hubmap-api-py-client==0.0.9
+
+# Test and analysis scripts
+argparse==1.4.0
+datatest==0.11.1
+deepdiff==6.7.1
diff --git a/src/uwsgi.ini b/src/uwsgi.ini
index 1a3ca51..15854af 100644
--- a/src/uwsgi.ini
+++ b/src/uwsgi.ini
@@ -9,9 +9,12 @@ module = wsgi:application
 # Send logs to stdout instead of file so docker picks it up and writes to AWS CloudWatch
 log-master=true
 
-# Master with 2 worker process (based on CPU number)
+# Master with 4 worker process (based on CPU number)
 master = true
-processes = 2
+processes = 4
+
+# Enable multithreading
+enable-threads = true
 
 # Use http socket for integration with nginx running on the same machine
 socket = localhost:5000
diff --git a/test/compare_responses.py b/test/compare_responses.py
new file mode 100755
index 0000000..7fb3b31
--- /dev/null
+++ b/test/compare_responses.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python3
+
+import argparse
+import requests
+import json
+from datatest import validate, ValidationError
+# https://zepworks.com/deepdiff/6.7.1/
+from deepdiff import DeepDiff
+import pprint
+
+
+UBKG_URL_PROD = 'https://ontology.api.hubmapconsortium.org'
+UBKG_URL_DEV = 'https://ontology-api.dev.hubmapconsortium.org'
+UBKG_URL_TEST = 'https://ontology-api.test.hubmapconsortium.org'
+
+URL_LOCAL = 'http://127.0.0.1:5002'
+
+DIFFERENCES_STATUS_CODE = 0
+DIFFERENCES_RESPONSE = 0
+ENDPOINTS_PROCESSED = 0
+STATUS_CODE_500_HOST_A = 0
+STATUS_CODE_500_HOST_B = 0
+
+
+class RawTextArgumentDefaultsHelpFormatter(
+    argparse.ArgumentDefaultsHelpFormatter,
+    argparse.RawTextHelpFormatter
+):
+    pass
+
+
+# https://docs.python.org/3/howto/argparse.html
+parser = argparse.ArgumentParser(
+    description="""
+    Check for differences between endpoints from two UBKG micro-services.
+    
+    For example executing:
+    % src/compare_responses.py https://ontology.api.hubmapconsortium.org http://127.0.0.1:5002 -d
+    Will compare the results from microservice on ontology.api.hubmapconsortium.org to the
+    one running on localhost using deepdiff to do the comparison of the output.
+    
+    Without using one of '-t', '-d', or '-V' only endpoint status differences will be flagged.
+    
+    For documentation on DeepDiff see: https://zepworks.com/deepdiff/6.7.1/diff.html
+    """,
+    formatter_class=RawTextArgumentDefaultsHelpFormatter)
+parser.add_argument('host_a', type=str, default=UBKG_URL_PROD,
+                    help='host to be compared too (expected)')
+parser.add_argument('host_b', type=str, default=URL_LOCAL,
+                    help='host to check for differences')
+parser.add_argument("-t", "--textual", action="store_true",
+                    help='show textual differences')
+parser.add_argument("-d", "--deepdiff", action="store_true",
+                    help='show differences using DeepDiff')
+parser.add_argument("-V", "--validate", action="store_true",
+                    help='show differences using Validate')
+parser.add_argument("-v", "--verbose", action="store_true",
+                    help='increase output verbosity')
+
+args = parser.parse_args()
+
+
+def endpoint_get_diff(host_a: str, host_b: str, path) -> None:
+    resp_a = requests.get(f"{host_a}{path}")
+    resp_b = requests.get(f"{host_b}{path}")
+    resp_diff(host_a, host_b, path, resp_a, resp_b)
+
+
+def endpoint_post_diff(host_a: str, host_b: str, path: str, data: dict) -> None:
+    resp_a = requests.post(f"{host_a}{path}", json=data)
+    resp_b = requests.post(f"{host_b}{path}", json=data)
+    resp_diff(host_a, host_b, path, resp_a, resp_b)
+
+
+def resp_diff(host_a: str, host_b: str, path: str, resp_a, resp_b) -> None:
+    global DIFFERENCES_STATUS_CODE, DIFFERENCES_RESPONSE, ENDPOINTS_PROCESSED
+    global STATUS_CODE_500_HOST_A, STATUS_CODE_500_HOST_B
+    print(f"Checking path: {path} ", end='')
+    ENDPOINTS_PROCESSED += 1
+    if resp_a.status_code == 500:
+        STATUS_CODE_500_HOST_A += 1
+    if resp_b.status_code == 500:
+        STATUS_CODE_500_HOST_B += 1
+    if resp_a.status_code != resp_b.status_code:
+        print(f"\nSTATUS CODES DIFFER: {host_a}:{resp_a.status_code}; {host_b}:{resp_b.status_code}")
+        DIFFERENCES_STATUS_CODE += 1
+        return
+    if resp_a.text != resp_b.text:
+        DIFFERENCES_RESPONSE += 1
+        if args.textual is True:
+            print(f"\nDIFFERENCES (Textual): {host_a}:{resp_a.text}; {host_b}:{resp_b.text}")
+        resp_a_dict: dict = json.loads(resp_a.text)
+        resp_b_dict: dict = json.loads(resp_b.text)
+        if args.deepdiff is True:
+            # https://zepworks.com/deepdiff/6.7.1/diff.html
+            deepdiff = DeepDiff(resp_b_dict, resp_a_dict,
+                                ignore_order=True, ignore_string_case=True,
+                                report_repetition=True, verbose_level=2
+                                )
+            print('DIFFERENCE (DeepDiff):')
+            pp = pprint.PrettyPrinter(indent=4)
+            pp.pprint(deepdiff)
+        if args.validate is True:
+            try:
+                validate(resp_b_dict, resp_a_dict)
+            except ValidationError as ve:
+                # https://datatest.readthedocs.io/en/stable/reference/datatest-core.html#datatest.ValidationError
+                print('DIFFERENCES (Validate):')
+                print(f"Expected (host_a): {ve.differences[0].expected}")
+                print(f"Found (host_b): {ve.differences[0].invalid}")
+                print()
+
+        return
+    print('OK!')
+
+
+def diff(host_a: str, host_b: str) -> None:
+    endpoint_post_diff(host_a, host_b, '/assayname', {"name": "bulk-RNA"})
+    endpoint_get_diff(host_a, host_b, '/assaytype?application_context=HUBMAP')
+    endpoint_get_diff(host_a, host_b, '/assaytype/bulk-RNA?application_context=HUBMAP')
+    endpoint_get_diff(host_a, host_b, '/datasets?application_context=HUBMAP')
+    endpoint_get_diff(host_a, host_b, '/organs?application_context=HUBMAP')
+    endpoint_get_diff(host_a, host_b, '/organs?application_context=SENNET')
+    endpoint_get_diff(host_a, host_b, '/organs/by-code?application_context=HUBMAP')
+    endpoint_get_diff(host_a, host_b, '/relationships/gene/MMRN1')
+    endpoint_get_diff(host_a, host_b, '/valueset?child_sabs=OBI&parent_sab=HUBMAP&parent_code=C001000')
+    endpoint_get_diff(host_a, host_b, '/genes-info?page=1&genes_per_page=3')
+    endpoint_get_diff(host_a, host_b, '/genes-info?page=last&genes_per_page=3')
+    endpoint_get_diff(host_a, host_b, '/genes-info?genes_per_page=3&starts_with=B')
+    endpoint_get_diff(host_a, host_b, '/genes/MMRN1')
+    endpoint_get_diff(host_a, host_b, '/proteins-info?page=1&proteins_per_page=3')
+    endpoint_get_diff(host_a, host_b, '/proteins-info?page=last&proteins_per_page=3')
+    endpoint_get_diff(host_a, host_b, '/proteins-info?proteins_per_page=3&starts_with=B')
+    endpoint_get_diff(host_a, host_b, '/proteins/MMRN1_HUMAN')
+    endpoint_get_diff(host_a, host_b, '/celltypes-info?page=1&proteins_per_page=3')
+    endpoint_get_diff(host_a, host_b, '/celltypes-info?page=last&proteins_per_page=3')
+    endpoint_get_diff(host_a, host_b, '/celltypes-info?proteins_per_page=3&starts_with=B')
+    endpoint_get_diff(host_a, host_b, '/celltypes/0002138')
+    endpoint_get_diff(host_a, host_b, '/valueset?parent_sab=SENNET&parent_code=C020076&child_sabs=SENNET')
+    endpoint_get_diff(host_a, host_b, '/valueset?parent_sab=SENNET&parent_code=C050020&child_sabs=SENNET')
+    endpoint_get_diff(host_a, host_b, '/valueset?parent_sab=SENNET&parent_code=C000012&child_sabs=SENNET')
+    endpoint_get_diff(host_a, host_b, '/field-descriptions')
+    endpoint_get_diff(host_a, host_b, '/field-descriptions/acquisition_instrument_model')
+    endpoint_get_diff(host_a, host_b, '/field-descriptions/acquisition_instrument_model?test=X')
+    endpoint_get_diff(host_a, host_b, '/field-descriptions/acquisition_instrument_model?source=X')
+    endpoint_get_diff(host_a, host_b, '/field-descriptions/acquisition_instrument_model?source=HMFIELD')
+    endpoint_get_diff(host_a, host_b, '/field-types')
+    endpoint_get_diff(host_a, host_b, '/field-types/acquisition_instrument_model')
+    endpoint_get_diff(host_a, host_b, '/field-types/acquisition_instrument_model?test=x')
+    endpoint_get_diff(host_a, host_b, '/field-types/acquisition_instrument_model?mapping_source=X')
+    endpoint_get_diff(host_a, host_b, '/field-types/acquisition_instrument_model?mapping_source=HMFIELD')
+    endpoint_get_diff(host_a, host_b, '/field-types/acquisition_instrument_model?type_source=X')
+    endpoint_get_diff(host_a, host_b, '/field-types/acquisition_instrument_model?type_source=HMFIELD')
+    endpoint_get_diff(host_a, host_b, '/field-types/acquisition_instrument_model?type=X')
+    endpoint_get_diff(host_a, host_b, '/field-types/acquisition_instrument_model?type=string')
+    endpoint_get_diff(host_a, host_b, '/field-types-info')
+    endpoint_get_diff(host_a, host_b, '/field-types-info?test=x')
+    endpoint_get_diff(host_a, host_b, '/field-types-info?type_source=x')
+    endpoint_get_diff(host_a, host_b, '/field-types-info?type_source=HMFIELD')
+    endpoint_get_diff(host_a, host_b, '/field-assays')
+    endpoint_get_diff(host_a, host_b, '/field-assays/acquisition_instrument_model')
+    endpoint_get_diff(host_a, host_b, '/field-assays?test=X')
+    endpoint_get_diff(host_a, host_b, '/field-assays?assay_identifier=X')
+    endpoint_get_diff(host_a, host_b, '/field-assays?assay_identifier=snRNAseq')
+    endpoint_get_diff(host_a, host_b, '/field-assays/acquisition_instrument_model?assay_identifier=snRNAseq')
+    endpoint_get_diff(host_a, host_b, '/field-assays?data_type=X')
+    endpoint_get_diff(host_a, host_b, '/field-assays?data_type=seqFISH')
+    endpoint_get_diff(host_a, host_b, '/field-assays/acquisition_instrument_model?data_type=seqFISH')
+    endpoint_get_diff(host_a, host_b, '/field-assays?dataset_type=x')
+    endpoint_get_diff(host_a, host_b, '/field-assays?dataset_type=RNAseq')
+    endpoint_get_diff(host_a, host_b, '/field-assays/acquisition_instrument_model?dataset_type=RNAseq')
+    endpoint_get_diff(host_a, host_b, '/field-entities')
+    endpoint_get_diff(host_a, host_b, '/field-entities?test=X')
+    endpoint_get_diff(host_a, host_b, '/field-entities/acquisition_instrument_model')
+    endpoint_get_diff(host_a, host_b, '/field-entities/acquisition_instrument_model?source=x')
+    endpoint_get_diff(host_a, host_b, '/field-entities/acquisition_instrument_model?source=HMFIELD')
+    endpoint_get_diff(host_a, host_b, '/field-entities/acquisition_instrument_model?entity=x')
+    endpoint_get_diff(host_a, host_b, '/field-entities/acquisition_instrument_model?entity=dataset')
+    endpoint_get_diff(host_a, host_b, '/field-schemas')
+    endpoint_get_diff(host_a, host_b, '/field-schemas?test=x')
+    endpoint_get_diff(host_a, host_b, '/field-schemas/acquisition_instrument_model')
+    endpoint_get_diff(host_a, host_b, '/field-schemas?source=x')
+    endpoint_get_diff(host_a, host_b, '/field-schemas?source=HMFIELD')
+    endpoint_get_diff(host_a, host_b, '/field-schemas?schema=x')
+    endpoint_get_diff(host_a, host_b, '/field-schemas?schema=imc3d')
+
+
+diff(args.host_a, args.host_b)
+print(f"\nEndpoints processed: {ENDPOINTS_PROCESSED}; "
+      f"Response Differences: {DIFFERENCES_RESPONSE}"
+      f"\nStatus Code Differences: {DIFFERENCES_STATUS_CODE}; "
+      f"Status Codes 500 {args.host_a}: {STATUS_CODE_500_HOST_A}; "
+      f"Status Codes 500 {args.host_b}: {STATUS_CODE_500_HOST_B} "
+      )
+print("\nDone!")
diff --git a/test/test_api.sh b/test/test_api.sh
new file mode 100755
index 0000000..42f18c8
--- /dev/null
+++ b/test/test_api.sh
@@ -0,0 +1,705 @@
+#!/bin/bash
+##########
+# Test script for UBKG API
+##########
+
+
+set -e
+set -u
+###########
+# Help function
+##########
+Help()
+{
+   # Display Help
+   echo ""
+   echo "****************************************"
+   echo "HELP: UBKG API test script"
+   echo
+   echo "Syntax: ./test_api.sh [-option]..."
+   echo "option"
+   echo "-v     test environment: l (local), d (DEV), or p (PROD)"
+}
+
+#####
+# Get options
+while getopts ":hv:" option; do
+   case $option in
+      h) # display Help
+         Help
+         exit;;
+      v) # environment
+         env=$OPTARG;;
+      \?) # Invalid option
+         echo "Error: Invalid option"
+         exit;;
+   esac
+done
+
+# Environment URLs.
+UBKG_URL_PROD=https://ontology.api.hubmapconsortium.org
+UBKG_URL_DEV=https://ontology-api.dev.hubmapconsortium.org
+UBKG_URL_LOCAL=http://127.0.0.1:5002
+
+# Map to selected API environment.
+case "$env" in
+  l) # local
+    UBKG_URL="${UBKG_URL:-$UBKG_URL_LOCAL}";;
+  d) # DEV
+    UBKG_URL="${UBKG_URL:-$UBKG_URL_DEV}";;
+  p) # PROD
+    UBKG_URL="${UBKG_URL:-$UBKG_URL_PROD}";;
+  \?) # default to local machine
+    UBKG_URL="${UBKG_URL:-$UBKG_URL_LOCAL}";;
+
+esac
+
+echo "Using UBKG at: ${UBKG_URL}" | tee test.out
+echo "For these tests, only first 60 characters of output from HTTP 200 returns displayed." | tee -a test.out
+echo "To review response bodies in detail, call endpoints individually." | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+# $ ./test_api.sh
+# Using UBKG at: https://ontology-api.dev.hubmapconsortium.org
+# $ (export UBKG_URL=http://127.0.0.1:5002; ./test_api.sh)
+# Using UBKG at: http://127.0.0.1:5002
+
+#--------------------------------------------
+echo "TESTS FOR: assayname POST" | tee -a test.out
+echo "SIGNATURE: /assayname" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "/assayname_POST with bulk-RNA => should return 200" | tee -a test.out
+curl --request POST \
+ --url "${UBKG_URL}/assayname" \
+ --header "Content-Type: application/json" \
+ --data '{"name": "bulk-RNA"}' | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TESTS FOR: assaytypes GET" | tee -a test.out
+echo "SIGNATURE: /assaytypes?application_context=<context>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+echo "/assaytype?application_context=HUBMAP GET => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/assaytype?application_context=HUBMAP" \
+ --header "Accept: application/json"| cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+
+echo "TESTS FOR: assaytypes GET" | tee -a test.out
+echo "SIGNATURE: /assaytypes/<data_type name>?application_context=<context>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+echo "/assaytypes/bulk-RNA?application_context=HUBMAP => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/assaytype/bulk-RNA?application_context=HUBMAP" \
+ --header "Accept: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TESTS FOR: datasets GET" | tee -a test.out
+echo "SIGNATURE: /datasets?application_context=<context>&data_type=<data_type>&description=<description>&alt_name=<alt_name>&primary=<true or false>&contains_pii=<true or false>&vis_only=<true or false>&vitessce_hint=<hint>&dataset_provider=<provider>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+echo "/datasets?application_context=HUBMAP => should return 200" | tee -a test.out
+
+curl --request GET \
+ --url "${UBKG_URL}/datasets?application_context=HUBMAP" \
+ --header "Accept: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+
+echo "TESTS FOR: organs GET" | tee -a test.out
+echo "SIGNATURE: /organs?application_context=<context>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "1. /organs => missing application context; should return custom 400" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/organs" \
+ --header "Accept: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "2. /organs?application_context=HUBMAP => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/organs?application_context=HUBMAP" \
+ --header "Accept: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "3. /organs?application_context=SENNET => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/organs?application_context=SENNET" \
+ --header "Accept: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TESTS FOR: organs/by-code GET" | tee -a test.out
+echo "SIGNATURE: /organs/by-code?application_context=<context>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "/organs/by-code?application_context=HUBMAP => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/organs/by-code?application_context=HUBMAP" \
+ --header "Accept: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TESTS FOR: relationships/gene GET" | tee -a test.out
+echo "SIGNATURE: /relationships/gene/<HGNC symbol>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "relationships/gene/MMRN1 => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/relationships/gene/MMRN1" \
+ --header "Content-Type: application/json"| cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TESTS FOR: valueset GET" | tee -a test.out
+echo "SIGNATURE: /valueset?child_sabs=<list of sabs.&parent_sab=<sab>&parent_code=<code>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "/valueset?child_sabs=OBI&parent_sab=HUBMAP&parent_code=C001000 => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/valueset?child_sabs=OBI&parent_sab=HUBMAP&parent_code=C001000" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TESTS FOR: genes-info GET" | tee -a test.out
+echo "SIGNATURE: /gene_list?page=<page>&genes_per_page=<number>&starts_with=<characters>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+# Test for genes-info endpoint
+echo "1. /genes-info?page=1&genes_per_page=3 => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/genes-info?page=1&genes_per_page=3" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "2. /genes-info?page=last&genes_per_page=3 => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/genes-info?page=last&genes_per_page=3" \
+ --header "Content-Type: application/json"| cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "3. /genes-info?genes_per_page=3&starts_with=B => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/genes-info?genes_per_page=3&starts_with=B" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+# Test for genes endpoint.
+echo "TESTS FOR: genes GET" | tee -a test.out
+echo "SIGNATURE: /genes/<HGNC symbol>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "/genes/MMRN1 => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/genes/MMRN1" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+# Test for proteins-info endpoint.
+echo "TESTS FOR: proteins-info GET" | tee -a test.out
+echo "SIGNATURE: /proteins_info?page=<page>&proteins_per_page=<number>&starts_with=<characters>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "1. /proteins-info?page=1&proteins_per_page=3 => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/proteins-info?page=1&proteins_per_page=3" \
+ --header "Content-Type: application/json"| cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "2. /proteins-info?page=last&proteins_per_page=3 => should return 200" | tee -a test.out
+echo "SHOULD RETURN 200"
+curl --request GET \
+ --url "${UBKG_URL}/proteins-info?page=last&proteins_per_page=3" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "3. /proteins-info?proteins_per_page=3&starts_with=B => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/proteins-info?proteins_per_page=3&starts_with=B" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TESTS FOR: proteins GET" | tee -a test.out
+echo "SIGNATURE: /genes/<UniProtKB symbol>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+# Test for proteins endpoint.
+echo "/proteins/MMRN1_HUMAN => should return 200" | tee -a test.out
+echo "SHOULD RETURN 200"
+curl --request GET \
+ --url "${UBKG_URL}/proteins/MMRN1_HUMAN" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TESTS FOR: celltypes-info GET" | tee -a test.out
+echo "SIGNATURE: /celltypesinfo?page=<page>&cell_types_per_page=<number>&starts_with=<characters>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "1. /celltypes-info?page=1&proteins_per_page=3 => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/celltypes-info?page=1&proteins_per_page=3" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "2. /celltypes-info?page=last&proteins_per_page=3 => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/celltypes-info?page=last&proteins_per_page=3" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "2. /celltypes-info?proteins_per_page=3&starts_with=B => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/celltypes-info?proteins_per_page=3&starts_with=B" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+# Test for celltypes endpoint.
+echo "TESTS FOR: celltypes GET" | tee -a test.out
+echo "SIGNATURE: /celltypesinfo/<CL code>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "/celltypes/0002138 => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/celltypes/0002138" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TEST FOR: SENNET source types" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/valueset?parent_sab=SENNET&parent_code=C020076&child_sabs=SENNET" \
+ --header "Content-Type: application/json" | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TEST FOR: SENNET sample categories" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/valueset?parent_sab=SENNET&parent_code=C050020&child_sabs=SENNET" \
+ --header "Content-Type: application/json" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TEST FOR: SENNET entities" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/valueset?parent_sab=SENNET&parent_code=C000012&child_sabs=SENNET" \
+ --header "Content-Type: application/json" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TESTS FOR: field-descriptions GET" | tee -a test.out
+echo "SIGNATURE: /field-descriptions/<field name>?source=<source?>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "1./ field-descriptions => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-descriptions" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "2. /field-descriptions/acquisition_instrument_model => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-descriptions/acquisition_instrument_model" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "3. /field-descriptions/acquisition_instrument_model?test=X => invalid parameter; should return custom 400" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-descriptions/acquisition_instrument_model?test=X" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "4. /field-descriptions/acquisition_instrument_model?source=X => invalid source; should return custom 400" | tee -a test.out
+echo "SHOULD RETURN 400; invalid parameter value"
+curl --request GET \
+ --url "${UBKG_URL}/field-descriptions/acquisition_instrument_model?source=X" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "5. field-descriptions/acquisition_instrument_model?source=HMFIELD = > should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-descriptions/acquisition_instrument_model?source=HMFIELD" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TESTS FOR: field-types GET" | tee -a test.out
+echo "SIGNATURE: /field-types/<field name>?mapping_source=<source>&type_source=<source>&type=<type>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "1. /field-types => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-types" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "2. /field-types/acquisition_instrument_model => should return 200" | tee -a test.out
+echo "SHOULD RETURN 200"
+curl --request GET \
+ --url "${UBKG_URL}/field-types/acquisition_instrument_model" \
+ --header "Content-Type: application/json"  | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "3. field-types/acquisition_instrument_model?test=x => invalid parameter: should return 400" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-types/acquisition_instrument_model?test=x" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "4. field-types/acquisition_instrument_model?mapping_source=X => invalid mapping source; should return 400" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-types/acquisition_instrument_model?mapping_source=X" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "5. field-types/acquisition_instrument_model?mapping_source=HMFIELD => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-types/acquisition_instrument_model?mapping_source=HMFIELD" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "6. field-types/acquisition_instrument_model?type_source=X => invalid type_source; should return 400" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-types/acquisition_instrument_model?type_source=X" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "7. field-types/acquisition_instrument_model?type_source=HMFIELD => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-types/acquisition_instrument_model?type_source=HMFIELD" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "8. field-types/acquisition_instrument_model?type=X => invalid type; should return 404" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-types/acquisition_instrument_model?type=X" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "9. field-types/acquisition_instrument_model?type=string => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-types/acquisition_instrument_model?type=string" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TESTS FOR: field-types-info GET" | tee -a test.out
+echo "SIGNATURE: /field-types-info?type_source=<source>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "1. /field-types-info => SHOULD RETURN 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-types-info" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "2. /field-types-info?test=x => invalid parameter; should return 400" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-types-info?test=x" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "3. /field-types-info?type_source=x => invalid type_source; should return 400" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-types-info?type_source=x" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "4. field-types-info?type_source=HMFIELD => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-types-info?type_source=HMFIELD" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TESTS FOR: field-assays GET" | tee -a test.out
+echo "SIGNATURE: /field-assays/<assay_identifier>?data_type=<data_type>&dataset_type=<dataset_type" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "1. /field-assays => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-assays" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "2. /field-assays/acquisition_instrument_model => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-assays/acquisition_instrument_model" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "3. field-assays/acquisition_instrument_model?test=X => invalid parameter; should return 400" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-assays/acquisition_instrument_model?test=X" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "4. /field-assays?assay_identifier=X => no results; should return 404" | tee -a test.out
+echo "SHOULD RETURN 404; no results"
+curl --request GET \
+ --url "${UBKG_URL}/field-assays?assay_identifier=X" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "5. /field-assays?assay_identifier=snRNAseq => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-assays?assay_identifier=snRNAseq" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "5. /field-assays?data_type=X => no results; should return 404" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-assays?data_type=X" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "6. /field-assays?data_type=seqFISH => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-assays?data_type=seqFISH" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "7. field-assays?dataset_type=X => no results; should return 404" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-assays?dataset_type=x" \
+ --header "Content-Type: application/json"
+echo
+
+echo "8. /field-assays?dataset_type=RNAseq => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-assays?dataset_type=RNAseq" \
+ --header "Content-Type: application/json"| cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TESTS FOR: field-entities GET" | tee -a test.out
+echo "SIGNATURE: /field-entities/<field_name>?source=<source>&entity=<entity>&application_context=<context>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "1. /field-entities => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-entities" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "2. /field-entities?test=X => invalid parameter; should return 400" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-entities?test=X" \
+ --header "Content-Type: application/json"
+echo
+
+echo "3. /field-entities/acquisition_instrument_model => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-entities/acquisition_instrument_model" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "4. /field-entities/acquisition_instrument_model?source=x => invalid source; should return 400" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-entities/acquisition_instrument_model?source=x" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "5. /field-entities/acquisition_instrument_model?source=HMFIELD => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-entities/acquisition_instrument_model?source=HMFIELD" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "6. /field-entities/acquisition_instrument_model?entity=x => no results; should return 404" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-entities/acquisition_instrument_model?entity=x" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "7. /field-entities/acquisition_instrument_model?entity=dataset => should return 200"
+echo "SHOULD RETURN 200"
+curl --request GET \
+ --url "${UBKG_URL}/field-entities/acquisition_instrument_model?entity=dataset" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "TESTS FOR: field-schemas GET" | tee -a test.out
+echo "SIGNATURE: /field-entities/<field_name>?source=<source>&schema=<schema>" | tee -a test.out
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "1. /field-schemas => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-schemas" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "2. /field-schemas?test=x => invalid parameter; should return 400" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-schemas?test=x" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+
+echo "3. /field-schemas/acquisition_instrument_model => should return 200" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-schemas/acquisition_instrument_model" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "4. /field-schemas/acquisition_instrument_model?source=x => invalid parameter value; should return 400" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-schemas?source=x" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "5. /field-schemas/acquisition_instrument_model?source=HMFIELD => should return 200" | tee -a test.out
+echo "SHOULD RETURN 200"
+curl --request GET \
+ --url "${UBKG_URL}/field-schemas?source=HMFIELD" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "6. /field-schemas/acquisition_instrument_model?schema=x => no results; should return 404" | tee -a test.out
+curl --request GET \
+ --url "${UBKG_URL}/field-schemas?schema=x" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+
+echo "7. /field-schemas/acquisition_instrument_model?schema=imc3d => should return 200" | tee -a test.out
+echo "SHOULD RETURN 200"
+curl --request GET \
+ --url "${UBKG_URL}/field-schemas?schema=imc3d" \
+ --header "Content-Type: application/json" | cut -c1-60 | tee -a test.out
+echo
+echo | tee -a test.out
+echo | tee -a test.out
+echo