Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Comprehensive API Testing #475

Merged
merged 5 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,15 @@ jobs:
- name: Dump docker logs before tests
uses: jwalton/gh-docker-logs@v1

- name: Run tests with pytest
- name: Install dependencies
run: poetry install

- name: Run API tests with pytest
run: |
poetry run pytest --timeout=120 -m api

- name: Run integration tests with pytest
run: |
poetry install
poetry run pytest --timeout=600 -m "integration"

- name: Run worker scale tests with pytest
Expand Down Expand Up @@ -230,9 +236,15 @@ jobs:
- name: Dump docker logs before tests
uses: jwalton/gh-docker-logs@v1

- name: Run tests with pytest
- name: Install dependencies
run: poetry install

- name: Run API tests with pytest
run: |
poetry run pytest --timeout=120 -m api

- name: Run integration tests with pytest
run: |
poetry install
poetry run pytest --timeout=600 -m "integration"

- name: Run worker scale tests with pytest
Expand Down
15 changes: 11 additions & 4 deletions alfalfa_web/server/api-v2.js
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,14 @@ router.get("/runs/:runId/points/:pointId", (req, res, next) => {
* description: The point was successfully updated
*/
router.put("/runs/:runId/points/:pointId", (req, res, next) => {
// TODO Confirm that point isn't an OUTPUT type
const { value } = req.body;

if (req.point.point_type == "OUTPUT") {
return res
.status(400)
.json({ message: `Point '${req.point.ref_id}' is of type '${req.point.point_type}' and cannot be written to` });
}

if (value !== null) {
const error = validate(
{ value },
Expand Down Expand Up @@ -687,7 +694,7 @@ router.delete("/runs/:runId", (req, res, next) => {
* schema:
* $ref: '#/components/schemas/Error'
*/
router.post("/runs/:runId/start", async (req, res, next) => {
router.post("/runs/:runId/start", (req, res, next) => {
const { body } = req;

const timeValidator = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
Expand Down Expand Up @@ -777,7 +784,7 @@ router.post("/runs/:runId/advance", (req, res, next) => {
*/
router.post("/runs/:runId/stop", (req, res, next) => {
// If the run is already stopping or stopped there is no need to send message
if (["STOPPING", "STOPPED", "COMPLETE"].includes(req.run.status)) {
if (["STOPPING", "COMPLETE", "ERROR", "READY"].includes(req.run.status)) {
res.sendStatus(204);
}
api
Expand Down Expand Up @@ -1220,7 +1227,7 @@ router.get("/simulations", async (req, res, next) => {
.catch(next);
});

router.get("*", (req, res) => res.status(404).json({ message: "Page not found" }));
router.all("*", (req, res) => res.status(404).json({ message: "Page not found" }));

router.use(errorHandler);

Expand Down
9 changes: 5 additions & 4 deletions alfalfa_web/server/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class AlfalfaAPI {
getPointsById = async (run, pointIds) => {
return Promise.all(
pointIds.map((pointId) => {
this.getPointById(run, pointId);
return this.getPointById(run, pointId);
})
);
};
Expand Down Expand Up @@ -173,7 +173,7 @@ class AlfalfaAPI {

removeRun = async (run) => {
// Delete run
const { deletedCount } = await this.run.deleteOne({ _id: run._id });
const { deletedCount } = await this.runs.deleteOne({ _id: run._id });

if (deletedCount == 1) {
// Delete points
Expand Down Expand Up @@ -258,7 +258,8 @@ class AlfalfaAPI {
};

listModels = async () => {
return this.getModels().map(this.formatModel);
const models = await this.getModels();
return models.map(this.formatModel);
};

getModels = async () => {
Expand All @@ -285,7 +286,7 @@ class AlfalfaAPI {
new GetObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: `uploads/${model.ref_id}/${model.model_name}`,
ResponseContentDisposition: `attachment; filename="${run.ref_id}.tar.gz"`
ResponseContentDisposition: `attachment; filename="${model.ref_id}.tar.gz"`
}),
{
expiresIn: 86400
Expand Down
503 changes: 252 additions & 251 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ influxdb = "^5.3.1"
# required for tests to pass right now. There are often issues when pinning to a branch
# and calling poetry update. I have had to completely remove my venv and reinstall in order
# to get the latest updates.
alfalfa-client = { git = "https://github.com/nrel/alfalfa-client.git", branch = "boptest_alignment" }
alfalfa-client = { git = "https://github.com/nrel/alfalfa-client.git", branch = "upload_model_check" }
# alfalfa-client = "~0.5.0"
autopep8 = "~2.0"
pre-commit = "~2.21"
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ extras = True
addopts =
--cov alfalfa_worker --cov-report term-missing
--verbose
-m "not integration and not fmu and not docker and not scale"
-m "not integration and not fmu and not docker and not scale and not api"
markers =
integration: marks tests as integration tests (deselect with '-m "not integration"')
fmu: mark tests that require fmu support, e.g., pyfmi (deselect with '-m "not fmu"')
Expand Down
1 change: 1 addition & 0 deletions tests/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""This module includes tests for the mechanical aspects of the api, but not necessarily all of the functionality."""
37 changes: 37 additions & 0 deletions tests/api/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import os
from pathlib import Path

import pytest
from alfalfa_client import AlfalfaClient
from requests import HTTPError


@pytest.fixture
def base_url():
return 'http://localhost/api/v2'


@pytest.fixture
def alfalfa_client():
return AlfalfaClient()


@pytest.fixture
def model_path():
return Path(os.path.dirname(__file__)) / "models" / "small_office"


@pytest.fixture
def model_id(alfalfa_client: AlfalfaClient, model_path):
return alfalfa_client.upload_model(model_path)


@pytest.fixture
def run_id(alfalfa_client: AlfalfaClient, model_path):
run_id = alfalfa_client.submit(model_path)
yield run_id
try:
if alfalfa_client.status(run_id) not in ["COMPLETE", "ERROR", "STOPPING", "READY"]:
alfalfa_client.stop(run_id)
except HTTPError:
pass
92 changes: 92 additions & 0 deletions tests/api/models/small_office/measures/alfalfa_vars/measure.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# insert your copyright here

# see the URL below for information on how to write OpenStudio measures
# http://nrel.github.io/OpenStudio-user-documentation/reference/measure_writing_guide/

# start the measure
class AlfalfaVariables < OpenStudio::Measure::ModelMeasure
# human readable name
def name
return 'AlfalfaVariables'
end

# human readable description
def description
return 'Add custom variables for Alfalfa'
end

# human readable description of modeling approach
def modeler_description
return 'Add EMS global variables required by Alfalfa'
end

# define the arguments that the user will input
def arguments(model)
args = OpenStudio::Measure::OSArgumentVector.new
return args
end

def create_input(model, name, freq)
# The purpose of this function is to create an Alfalfa input that is accessible via python plugins

global = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, name)
global.setExportToBCVTB(true)

# The global variable's value must be sent to output an variable so that python programs can read it
# don't be mistaken, An OuputVariable object is created, but this is "input" to the simulation, from Alfalfa clients
global_ems_output = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(model, global)
global_ems_output.setName(name + "_EMS_Value")
global_ems_output.setUpdateFrequency("SystemTimestep")

# Request the custom ems output var creaed in the previous step
global_output = OpenStudio::Model::OutputVariable.new(global_ems_output.nameString(), model)
global_output.setName(name + "_Value")
global_output.setReportingFrequency(freq)
global_output.setKeyValue("EMS")
# Setting exportToBCVTB to true is optional, and will result in the output variable showing up in the Alfalfa api,
# this might be useful for confirmation of the input
global_output.setExportToBCVTB(true)

# repeat the previous steps for an "Enable" input
# This value will be 1 (instead of 0) anytime a client writes to the input via the Alfalfa api
global_enable = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, name + "_Enable")
global_enable.setExportToBCVTB(true)

global_enable_ems_output = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(model, global_enable)
global_enable_ems_output.setName(name + "_Enable_EMS_Value")
global_enable_ems_output.setUpdateFrequency("SystemTimestep")

global_enable_output = OpenStudio::Model::OutputVariable.new(global_enable_ems_output.nameString(), model)
global_enable_output.setName(name + "_Enable_Value")
global_enable_output.setReportingFrequency(freq)
global_enable_output.setKeyValue("EMS")
global_enable_output.setExportToBCVTB(true)
end

def create_output(model, var, key, name, freq)
new_var = OpenStudio::Model::OutputVariable.new(var, model)
new_var.setName(name)
new_var.setReportingFrequency(freq)
new_var.setKeyValue(key)
new_var.setExportToBCVTB(true)
end

# define what happens when the measure is run
def run(model, runner, user_arguments)
super(model, runner, user_arguments)

# Alfalfa inputs
# These can be set through the Alfalfa API, they will be available as OutputVariables
# in the simulation. Use them as you will
# Also see comments on the create_input method
create_input(model, "Test_Point_1", "Timestep")

# other OutputVariables might be custom python defined output variables,
# you should still be able to request them here, and as long as exportToBCVTB is true they will be available via Alfalfa

return true
end
end

# register the measure to be used by the application
AlfalfaVariables.new.registerWithApplication
54 changes: 54 additions & 0 deletions tests/api/models/small_office/measures/alfalfa_vars/measure.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?xml version="1.0"?>
<measure>
<schema_version>3.0</schema_version>
<name>alfalfa_variables</name>
<uid>86d51823-68a5-478a-b60b-60891c3c9b5f</uid>
<version_id>5c94ef46-d8e9-4ae9-864e-d884aba51509</version_id>
<version_modified>20201216T214230Z</version_modified>
<xml_checksum>356BE47F</xml_checksum>
<class_name>AlfalfaVariables</class_name>
<display_name>AlfalfaVariables</display_name>
<description>Add custom variables for Alfalfa</description>
<modeler_description>Add EMS global variables required by Alfalfa</modeler_description>
<arguments />
<outputs />
<provenances />
<tags>
<tag>HVAC.HVAC Controls</tag>
</tags>
<attributes>
<attribute>
<name>Measure Type</name>
<value>ModelMeasure</value>
<datatype>string</datatype>
</attribute>
<attribute>
<name>Intended Software Tool</name>
<value>Apply Measure Now</value>
<datatype>string</datatype>
</attribute>
<attribute>
<name>Intended Software Tool</name>
<value>OpenStudio Application</value>
<datatype>string</datatype>
</attribute>
<attribute>
<name>Intended Software Tool</name>
<value>Parametric Analysis Tool</value>
<datatype>string</datatype>
</attribute>
</attributes>
<files>
<file>
<version>
<software_program>OpenStudio</software_program>
<identifier>3.1.0</identifier>
<min_compatible>3.1.0</min_compatible>
</version>
<filename>measure.rb</filename>
<filetype>rb</filetype>
<usage_type>script</usage_type>
<checksum>A9E1EEF2</checksum>
</file>
</files>
</measure>
Loading
Loading