Skip to content

Commit 7ba6175

Browse files
committed
add integration test
1 parent 06a03f5 commit 7ba6175

8 files changed

Lines changed: 718 additions & 89 deletions

File tree

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: integration-test
2+
3+
on:
4+
push:
5+
6+
jobs:
7+
docker-build-and-test:
8+
name: Build Docker and Test Server
9+
runs-on: ubuntu-latest
10+
env:
11+
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
12+
NOAA_TOKEN: ${{ secrets.NOAA_TOKEN }}
13+
steps:
14+
- name: Checkout Code
15+
uses: actions/checkout@v3
16+
17+
- name: Set up Docker
18+
uses: docker/setup-buildx-action@v2
19+
20+
- name: Build Docker Image
21+
run: |
22+
docker build -t bsyncr-server .
23+
24+
- name: Run Docker Container
25+
run: |
26+
docker run -p 8080:5000 bsyncr-server
27+
echo "Docker container started successfully"
28+
29+
- name: Extra wait
30+
run: |
31+
sleep 10
32+
33+
- name: Health check
34+
run: |
35+
curl -f http://localhost:8080/health || exit 1
36+
37+
- name: Post BuildingSync file
38+
run: |
39+
curl -X POST "http://localhost:8080/?model_type=SLR" \
40+
-F "file=@tests/data/ex_bsync.xml" \
41+
--max-time 120 \
42+
--output ./tests/slr_results.zip
43+
# check if the slr_results.zip file exists, error if not
44+
if [ ! -f ./tests/slr_results.zip ]; then
45+
echo "Error: slr_results.zip not found!"
46+
exit 1
47+
fi
48+
echo "slr_results.zip file exists, proceeding with next steps."
49+
- name: Cleanup Docker Container
50+
run: |
51+
docker stop bsyncr-server
52+
docker rm bsyncr-server

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
.DS_Store
22
.python-version
33
.vscode
4+
.ruff_cache
45

56
node_modules
7+
tests/*.zip

README.md

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# bsyncr server
22

3-
A light HTTP wrapper around the bsyncr R package.
3+
A light HTTP wrapper around the `bsyncr` R package.
44

55
## Setup
66

@@ -19,12 +19,49 @@ https://www.ncdc.noaa.gov/cdo-web/token
1919

2020
```bash
2121
# run the server on localhost:5000
22-
docker run -p 5000:5000 -v $(pwd):/usr/src/app -e NOAA_TOKEN=YOUR_TOKEN_HERE bsyncr_server:latest
22+
export NOAA_TOKEN=<YOUR_TOKEN>
23+
docker run -p 5000:5000 -v $(pwd):/usr/src/app -e NOAA_TOKEN bsyncr_server:latest
2324

2425
# run the server along with SEED (assuming SEED is being run with docker-compose)
2526
docker run \
2627
--network="seed_default" \
2728
--name="bsyncr-server" \
28-
-e NOAA_TOKEN=$NOAA_TOKEN \
29+
-e NOAA_TOKEN \
2930
seedplatform/bsyncr-server:latest
3031
```
32+
33+
## Running Example with Curl
34+
35+
To post via CURL for easy testing:
36+
37+
```bash
38+
curl -X POST "http://localhost:8080/?model_type=SLR" \
39+
-F "file=@tests/data/ex_bsync.xml" \
40+
--max-time 120 \
41+
--output ./tests/slr_results.zip
42+
43+
curl -X POST "http://localhost:8080/?model_type=3PH" \
44+
-F "file=@tests/data/ex_bsync.xml" \
45+
--max-time 120 \
46+
--output ./tests/3ph_results.zip
47+
```
48+
49+
## Running Example with Python
50+
51+
To post via python using requests:
52+
53+
```python
54+
import requests
55+
56+
with open("tests/data/ex_bsync.xml", "rb") as file_:
57+
response = requests.request(
58+
method="POST",
59+
url="http://localhost:8080/",
60+
files=[("file", file_)],
61+
params={"model_type": "4P"}, # can be SLR, 3PC, 3PH, 4P
62+
timeout=60 * 2, # timeout after two minutes
63+
)
64+
65+
print(response.status_code)
66+
print(response.text)
67+
```

bsyncr_server/__init__.py

Lines changed: 0 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +0,0 @@
1-
from io import BytesIO
2-
import json
3-
from os import path
4-
import subprocess
5-
import tempfile
6-
import zipfile
7-
8-
from testsuite.validate_sch import validate_schematron
9-
10-
from flask import Flask, jsonify, send_file, request
11-
12-
13-
app = Flask(__name__)
14-
R_SCRIPT_PATH = "/usr/src/app/bsyncr_server/lib/bsyncRunner.r"
15-
SCHEMATRON_FILE_PATH = "/usr/src/schematron/bsyncr_schematron.sch"
16-
INPUT_FILE_PATH = "/tmp/input.xml"
17-
OUTPUT_FILENAMES = ["result.xml", "plot.png"]
18-
ERROR_FILENAME = "error.json"
19-
MODEL_CHOICES = ["SLR", "3PC", "3PH", "4P"]
20-
21-
22-
def json_error(status_code, detail):
23-
return {"errors": [{"status": str(status_code), "detail": detail}]}, status_code
24-
25-
26-
@app.route("/", methods=["POST"])
27-
def root():
28-
if "file" not in request.files:
29-
return json_error(400, "No file in request")
30-
uploaded_file = request.files["file"]
31-
32-
uploaded_file.save(INPUT_FILE_PATH)
33-
try:
34-
errors = validate_schematron(SCHEMATRON_FILE_PATH, INPUT_FILE_PATH)
35-
except Exception as e:
36-
print(e)
37-
return json_error(400, "Failed to run schematron. Is your XML well-formed?")
38-
39-
if errors:
40-
41-
def format_failure(failure):
42-
return f"line {failure.line}: element {failure.element}: {failure.message}"
43-
44-
json_errors = [
45-
{"detail": format_failure(error), "status": "400"} for error in errors
46-
]
47-
return {"errors": json_errors}, 400
48-
49-
model_type = request.args.get("model_type")
50-
if model_type is None:
51-
return json_error(
52-
400,
53-
f"Invalid value for `model_type` query parameter. "
54-
f"Must provide one of the following: {', '.join(MODEL_CHOICES)}",
55-
)
56-
57-
with tempfile.TemporaryDirectory() as tmpdirname:
58-
completed_process = subprocess.run(
59-
["Rscript", R_SCRIPT_PATH, INPUT_FILE_PATH, model_type, tmpdirname],
60-
)
61-
62-
if completed_process.returncode != 0:
63-
error_filepath = f"{tmpdirname}/{ERROR_FILENAME}"
64-
if not path.exists(error_filepath):
65-
raise Exception(f'Expected to find error json at "{error_filepath}"')
66-
with open(error_filepath, "r") as f:
67-
r_error = json.load(f)
68-
return json_error(
69-
500, f"Unexpected error from bsyncr script: {r_error['message']}"
70-
)
71-
72-
zip_file = BytesIO()
73-
with zipfile.ZipFile(zip_file, "w") as zf:
74-
for filename in OUTPUT_FILENAMES:
75-
zf.write(f"{tmpdirname}/{filename}", arcname=filename)
76-
77-
zip_file.seek(0)
78-
return send_file(zip_file, attachment_filename="bsyncr.zip")
79-
80-
81-
@app.route("/health", methods=["GET"])
82-
def health_check():
83-
return jsonify({"status": "healthy"}), 200
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
library("nmecr")
44
library("bsyncr")
55
library("rjson")
6+
library("dplyr")
7+
library("ggplot2")
8+
69

710
run_analysis <- function(bsync_filepath, model_type) {
811
baseline_scenario_id <- "Scenario-bsyncr"
@@ -43,7 +46,7 @@ run_analysis <- function(bsync_filepath, model_type) {
4346
args <- commandArgs(trailingOnly=TRUE)
4447
if (length(args) != 3) {
4548
print('USAGE:')
46-
print('Rscript bsyncRunner.r bsync_input model_type output_directory')
49+
print('Rscript bsync_runner.r bsync_input model_type output_directory')
4750
print(' bsync_input: path to input file')
4851
print(' model_type: type of model to fit')
4952
print(' output_directory: directory to output files')

bsyncr_server/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212

1313
app = Flask(__name__)
14-
R_SCRIPT_PATH = "/usr/src/app/bsyncr_server/lib/bsyncRunner.r"
14+
R_SCRIPT_PATH = "/usr/src/app/bsyncr_server/lib/bsync_runner.r"
1515
SCHEMATRON_FILE_PATH = "/usr/src/schematron/bsyncr_schematron.sch"
1616
INPUT_FILE_PATH = "/tmp/input.xml"
1717
OUTPUT_FILENAMES = ["result.xml", "plot.png"]
@@ -75,7 +75,7 @@ def format_failure(failure):
7575
zf.write(f"{tmpdirname}/{filename}", arcname=filename)
7676

7777
zip_file.seek(0)
78-
return send_file(zip_file, attachment_filename="bsyncr.zip")
78+
return send_file(zip_file, download_name="bsyncr.zip", as_attachment=True)
7979

8080

8181
@app.route("/health", methods=["GET"])

cspell.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"version": "0.2",
3+
"ignorePaths": [],
4+
"dictionaryDefinitions": [],
5+
"dictionaries": [],
6+
"words": [
7+
"Arvada",
8+
"balancepoint",
9+
"bsync",
10+
"bsyncr",
11+
"crul",
12+
"dataframe",
13+
"datatypeid",
14+
"dplyr",
15+
"Drybulb",
16+
"elec",
17+
"NMEC",
18+
"nmecr",
19+
"NOAA",
20+
"noaakey",
21+
"rnoaa",
22+
"testthat",
23+
"tidyr"
24+
],
25+
"ignoreWords": [],
26+
"import": []
27+
}

0 commit comments

Comments
 (0)