diff --git a/python/SetupPython.cmake b/python/SetupPython.cmake index ef8c0678d7..8f09b3d3b4 100644 --- a/python/SetupPython.cmake +++ b/python/SetupPython.cmake @@ -35,6 +35,20 @@ else() endif() endif() +execute_process(COMMAND ${Python_EXECUTABLE} -c "import requests; print(requests.__version__)" + RESULT_VARIABLE _PyRequests_STATUS + OUTPUT_VARIABLE PyRequests_Version + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE +) +if(_PyRequests_STATUS AND NOT _PyRequests_STATUS EQUAL 0) + message(AUTHOR_WARNING "requests isn't installed on your system python, so some tests won't be run. Run `pip install requests`") + set(PyRequests_AVAILABLE OFF) +else() + message("Found Python requests: ${PyRequests_Version}") + set(PyRequests_AVAILABLE ON) +endif() + get_filename_component(Python_PROGRAM_NAME ${Python_EXECUTABLE} NAME) get_filename_component(RESOLVED_PYTHON_LIBRARY "${Python_LIBRARIES}" REALPATH) diff --git a/ruby/engine/measure_manager_server.rb b/ruby/engine/measure_manager_server.rb index 336c1db868..6bc56ac5fa 100644 --- a/ruby/engine/measure_manager_server.rb +++ b/ruby/engine/measure_manager_server.rb @@ -119,7 +119,7 @@ def do_POST (request, response) result = {} - data = JSON.parse(request.body, {:symbolize_names=>true}) + data = JSON.parse(request.body || "{}", {:symbolize_names=>true}) my_measures_dir = data[:my_measures_dir] if my_measures_dir @@ -132,7 +132,7 @@ def do_POST (request, response) result = [] - data = JSON.parse(request.body, {:symbolize_names=>true}) + data = JSON.parse(request.body || "{}", {:symbolize_names=>true}) uid = data[:uid] if uid @@ -156,8 +156,8 @@ def do_POST (request, response) result = [] - data = JSON.parse(request.body, {:symbolize_names=>true}) force_reload = false + data = JSON.parse(request.body || "{}", {:symbolize_names=>true}) # loop over all local BCL measures OpenStudio::LocalBCL.instance.measures.each do |local_measure| @@ -181,7 +181,7 @@ def do_POST (request, response) result = [] - data = JSON.parse(request.body, {:symbolize_names=>true}) + data = JSON.parse(request.body || "{}", {:symbolize_names=>true}) measures_dir = data[:measures_dir] ? data[:measures_dir] : @my_measures_dir force_reload = data[:force_reload] ? data[:force_reload] : false @@ -205,7 +205,7 @@ def do_POST (request, response) when "/compute_arguments" - data = JSON.parse(request.body, {:symbolize_names=>true}) + data = JSON.parse(request.body || "{}", {:symbolize_names=>true}) measure_dir = data[:measure_dir ] osm_path = data[:osm_path] force_reload = data[:force_reload] ? data[:force_reload] : false @@ -239,7 +239,7 @@ def do_POST (request, response) when "/create_measure" - data = JSON.parse(request.body, {:symbolize_names=>true}) + data = JSON.parse(request.body || "{}", {:symbolize_names=>true}) measure_dir = data[:measure_dir] # name = data[:name] # we do not take name as input @@ -263,7 +263,7 @@ def do_POST (request, response) when "/duplicate_measure" - data = JSON.parse(request.body, {:symbolize_names=>true}) + data = JSON.parse(request.body || "{}", {:symbolize_names=>true}) old_measure_dir = data[:old_measure_dir] measure_dir = data[:measure_dir] diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 74c557322a..c73df417a0 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -364,6 +364,16 @@ if(BUILD_TESTING) WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/Testing/" ) + if (PyRequests_AVAILABLE) + add_test(NAME OpenStudioCLI.test_measure_manager + COMMAND ${Python_EXECUTABLE} -m pytest --verbose --os-cli-path $ "${CMAKE_CURRENT_SOURCE_DIR}/test/test_measure_manager.py" + WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/Testing/" + ) + add_test(NAME OpenStudioCLI.Classic.test_measure_manager + COMMAND ${Python_EXECUTABLE} -m pytest --verbose --use-classic --os-cli-path $ "${CMAKE_CURRENT_SOURCE_DIR}/test/test_measure_manager.py" + WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/Testing/" + ) + endif() else() # TODO: Remove. Fallback on these for now, as I don't know if CI has pytest installed diff --git a/src/cli/MeasureManager.cpp b/src/cli/MeasureManager.cpp index c72ea444ce..9f24cae69c 100644 --- a/src/cli/MeasureManager.cpp +++ b/src/cli/MeasureManager.cpp @@ -5,6 +5,7 @@ #include "../utilities/core/Filesystem.hpp" #include "../utilities/core/FilesystemHelpers.hpp" #include "../utilities/core/StringHelpers.hpp" +#include "../utilities/time/DateTime.hpp" #include "../osversion/VersionTranslator.hpp" #include "../energyplus/ForwardTranslator.hpp" #include "../utilities/bcl/LocalBCL.hpp" @@ -140,7 +141,7 @@ Json::Value MeasureManager::internalState() const { osmInfo["checksum"] = v.checksum; osms.append(std::move(osmInfo)); } - result["osm"] = std::move(osms); + result["osms"] = std::move(osms); Json::Value idfs(Json::arrayValue); for (const auto& [k, v] : m_idfs) { @@ -149,15 +150,18 @@ Json::Value MeasureManager::internalState() const { idfInfo["checksum"] = v.checksum; idfs.append(std::move(idfInfo)); } - result["idf"] = std::move(idfs); + result["idfs"] = std::move(idfs); + // Json::Value measures(Json::arrayValue); auto& measures = result["measures"]; + measures = Json::arrayValue; for (const auto& [measureDirPath, bclMeasureInfo] : m_measures) { Json::Value mInfo(Json::objectValue); measures.append(bclMeasureInfo.measure.toJSON()); } auto& measureInfos = result["measure_info"]; + measureInfos = Json::arrayValue; // Json::Value measureInfos(Json::arrayValue); for (const auto& [measureDirPath, bclMeasureInfo] : m_measures) { for (const auto& [osmOrIdfPath, measureInfo] : bclMeasureInfo.measureInfos) { @@ -592,14 +596,17 @@ bool MeasureManagerServer::close() { return status == pplx::task_group_status::completed; } +void MeasureManagerServer::unknown_endpoint(web::http::http_request& message) { + const std::string uri = toString(web::http::uri::decode(message.relative_uri().path())); + message.reply(web::http::status_codes::BadRequest, toWebJSON(fmt::format("Error, unknown path '{}'", uri))); + print_feedback(message, web::http::status_codes::NotFound); +} + void MeasureManagerServer::handle_get(web::http::http_request message) { const std::string uri = toString(web::http::uri::decode(message.relative_uri().path())); if (uri == "/") { - Json::Value result; - result["status"] = "running"; - result["my_measures_dir"] = my_measures_dir.generic_string(); - message.reply(web::http::status_codes::OK, toWebJSON(result)); + handle_request(message, web::json::value(), &MeasureManagerServer::status); return; } @@ -609,7 +616,7 @@ void MeasureManagerServer::handle_get(web::http::http_request message) { return; } - message.reply(web::http::status_codes::BadRequest, toWebJSON(fmt::format("Error, unknown path '{}'", uri))); + unknown_endpoint(message); } void MeasureManagerServer::handle_post(web::http::http_request message) { @@ -667,8 +674,16 @@ void MeasureManagerServer::handle_post(web::http::http_request message) { return; } - message.reply(web::http::status_codes::NotFound, toWebJSON("404: Unknown Endpoint")); + unknown_endpoint(message); } + +MeasureManagerServer::ResponseType MeasureManagerServer::status([[maybe_unused]] const web::json::value& body) { + Json::Value result; + result["status"] = "running"; + result["my_measures_dir"] = my_measures_dir.generic_string(); + return {web::http::status_codes::OK, toWebJSON(result)}; +} + MeasureManagerServer::ResponseType MeasureManagerServer::internal_state([[maybe_unused]] const web::json::value& body) { Json::Value result; result["status"] = "running"; @@ -984,6 +999,15 @@ MeasureManagerServer::ResponseType MeasureManagerServer::duplicate_measure(const } } +void MeasureManagerServer::print_feedback(const web::http::http_request& message, web::http::status_code status_code) { + const std::string uri = toString(web::http::uri::decode(message.relative_uri().path())); + const std::string method = toString(message.method()); + const std::string http_version = message.http_version().to_utf8string(); + const std::string timestamp = openstudio::DateTime::now().toXsdDateTime(); + fmt::print(status_code == web::http::status_codes::OK ? stdout : stderr, "[{}] \"{} {} {}\" {}\n", openstudio::DateTime::now().toXsdDateTime(), + method, uri, http_version, status_code); +} + void MeasureManagerServer::handle_request(const web::http::http_request& message, const web::json::value& body, memRequestHandlerFunPtr request_handler) { @@ -991,23 +1015,28 @@ void MeasureManagerServer::handle_request(const web::http::http_request& message auto future_result = task.get_future(); // The task hasn't been started yet tasks.push_back(std::move(task)); // It gets queued, the **main** thread will process it + web::http::status_code status_code = web::http::status_codes::Created; try { auto result = future_result.get(); // This block until it's been processed - message.reply(result.status_code, result.body); + status_code = result.status_code; + message.reply(status_code, result.body); } catch (const std::exception& e) { constexpr auto msg = "MeasureManager Server encountered an error:\n\"{}\"\n"; fmt::print(msg, e.what()); + status_code = web::http::status_codes::InternalError; message.reply(web::http::status_codes::InternalError, fmt::format(msg, e.what())); } + print_feedback(message, status_code); } void MeasureManagerServer::do_tasks_forever() { - fmt::print("MeasureManager Ready"); + fmt::print("MeasureManager Ready\n"); fmt::print("Accepting requests on: {}\n", m_url); std::fflush(stdout); while (true) { auto task = tasks.wait_for_one(); task(); + std::fflush(stdout); } } diff --git a/src/cli/MeasureManager.hpp b/src/cli/MeasureManager.hpp index acd292c7c5..eaa3e04842 100644 --- a/src/cli/MeasureManager.hpp +++ b/src/cli/MeasureManager.hpp @@ -110,6 +110,7 @@ class MeasureManagerServer }; // Request handlers + ResponseType status(const web::json::value& body); ResponseType internal_state(const web::json::value& body); ResponseType reset(const web::json::value& body); ResponseType set(const web::json::value& body); @@ -129,6 +130,15 @@ class MeasureManagerServer void handle_get(web::http::http_request message); void handle_post(web::http::http_request message); static void handle_error(pplx::task& t); + + // Helper to return a 404 error + static void unknown_endpoint(web::http::http_request& message); + + // Print the request to the console (stdout if Ok, stderr otherwise) + // [2024-11-14T10:21:46+01:00] "POST /reset HTTP/1.1" 200 + // [2024-11-14T10:22:09+01:00] "GET /dsd HTTP/1.1" 400 + static void print_feedback(const web::http::http_request& message, web::http::status_code status_code); + MeasureManager m_measureManager; web::http::experimental::listener::http_listener m_listener; ThreadSafeDeque> tasks; diff --git a/src/cli/test/conftest.py b/src/cli/test/conftest.py index 88c8bc23e5..7b6a64dfa0 100644 --- a/src/cli/test/conftest.py +++ b/src/cli/test/conftest.py @@ -12,7 +12,7 @@ def validate_file(arg): def pytest_addoption(parser): parser.addoption("--os-cli-path", type=validate_file, help="Path to the OS CLI") # , required=True - + parser.addoption("--use-classic", action="store_true", help="Force use the Classic CLI") @pytest.fixture(scope="module") def osclipath(request): @@ -20,3 +20,8 @@ def osclipath(request): if cli_path is None: raise ValueError("You must supply --os-cli-path [Path]") return cli_path + +@pytest.fixture(scope="module") +def use_classic_cli(request): + use_classic = request.config.getoption("--use-classic") + return use_classic diff --git a/src/cli/test/test_measure_manager.py b/src/cli/test/test_measure_manager.py index 93a231fea6..95572ec808 100644 --- a/src/cli/test/test_measure_manager.py +++ b/src/cli/test/test_measure_manager.py @@ -27,12 +27,20 @@ HOST = "localhost" DEFAULT_MEASURES_DIR = Path("~/OpenStudio/Measures").expanduser() -BASE_INTERNAL_STATE: Dict[str, Any] = { - "idf": [], - "measure_info": None, - "measures": None, +BASE_INTERNAL_STATE_CLASSIC: Dict[str, Any] = { + "status": "running", + "my_measures_dir": DEFAULT_MEASURES_DIR.as_posix() + "/", + "osms": [], + "measures": [], + "measure_info": [], +} + +BASE_INTERNAL_STATE_LABS: Dict[str, Any] = { + "idfs": [], + "measure_info": [], + "measures": [], "my_measures_dir": DEFAULT_MEASURES_DIR.as_posix(), - "osm": [], + "osms": [], "status": "running", } @@ -48,8 +56,10 @@ def get_url(port: int): class MeasureManagerClient(requests.Session): """Stores the base url in the class so we don't repeat ourselves.""" - def __init__(self, port): + def __init__(self, port, is_classic=False): self.base_url = get_url(port=port) + self.is_classic = is_classic + self.base_state = deepcopy(BASE_INTERNAL_STATE_CLASSIC if is_classic else BASE_INTERNAL_STATE_LABS) super().__init__() def request(self, method, url, *args, **kwargs): @@ -68,12 +78,36 @@ def reset(self): def reset_and_assert_internal_state(self): self.reset() - assert self.internal_state() == BASE_INTERNAL_STATE + assert self.internal_state() == self.base_state + def prime_a_new_my_measures_dir_with_a_single_measure(self, my_measures_dir: Path) -> Path: + """Calls /set then /create_measure and return the new measure path under it.""" + my_measures_dir.mkdir(parents=True) + + r = self.post("/set", json={"my_measures_dir": str(my_measures_dir)}) + r.raise_for_status() + assert self.internal_state()["my_measures_dir"] == my_measures_dir.as_posix() + + measure_dir = my_measures_dir / "MyMeasure" + data = { + # "name": "name", + "measure_dir": str(measure_dir), + "display_name": "A ModelMeasure that depends on model", + "class_name": "ModelDependentMeasure", + "taxonomy_tag": "taxonomy_tag", + "measure_type": "ModelMeasure", + "measure_language": "Ruby", + "description": "This is the description", + "modeler_description": "This is the modeler description", + } + r = self.post(url="/create_measure", json=data) + r.raise_for_status + assert measure_dir.is_dir() + return measure_dir @pytest.fixture -def expected_internal_state(): - return deepcopy(BASE_INTERNAL_STATE) +def expected_internal_state(use_classic_cli): + return deepcopy(BASE_INTERNAL_STATE_CLASSIC if use_classic_cli else BASE_INTERNAL_STATE_LABS) def get_open_port_for_serving(): @@ -98,10 +132,15 @@ def get_open_port_for_serving(): return port -def launch_measure_manager_client(osclipath: Path) -> Tuple[subprocess.Popen, int]: +def launch_measure_manager_client(osclipath: Path, use_classic_cli: bool) -> Tuple[subprocess.Popen, int]: port = get_open_port_for_serving() base_url = get_url(port=port) - proc = subprocess.Popen([str(osclipath), "labs", "measure", "-s", str(port)]) # NOTE: remove 'labs' to test old CLI + cmd_args = [str(osclipath)] + if use_classic_cli: + cmd_args.append("classic") + cmd_args += ["measure", "-s", str(port)] + print(cmd_args) + proc = subprocess.Popen(cmd_args) ok = False while not ok: try: @@ -120,14 +159,14 @@ def launch_measure_manager_client(osclipath: Path) -> Tuple[subprocess.Popen, in @pytest.fixture -def measure_manager_client(osclipath): +def measure_manager_client(osclipath, use_classic_cli): """Launches a measure manager server and waits for it to be ready, then destroys at end.""" # If you want to attach to your debug CLI session you have launched in a debugger, just do this isntead # yield MeasureManagerClient(port=8094) # return # Regular - proc, port = launch_measure_manager_client(osclipath=osclipath) - yield MeasureManagerClient(port=port) + proc, port = launch_measure_manager_client(osclipath=osclipath, use_classic_cli=use_classic_cli) + yield MeasureManagerClient(port=port, is_classic=use_classic_cli) # if sys.platform == "win32": # proc.send_signal(signal.CTRL_C_EVENT) # else: @@ -164,8 +203,34 @@ def test_default_internal_state(measure_manager_client, expected_internal_state) assert measure_manager_client.internal_state() == expected_internal_state -def test_set_measures_dir(measure_manager_client, expected_internal_state): - my_measures_dir = Path("~/OpenStudio/Measures2").expanduser() +def test_set_measures_dir(measure_manager_client, expected_internal_state, tmp_path): + my_measures_dir = tmp_path / 'Measures' + + r = measure_manager_client.post("/set", json={"BAD": str(my_measures_dir)}) + # The Classic CLI returns 200 even though it didn't set anything, the C++ error handling is better + if measure_manager_client.is_classic: + assert r.status_code == 200 + assert not r.json() + else: + assert r.status_code == 400 + assert r.json() == "Missing the my_measures_dir in the post data" + assert measure_manager_client.internal_state() == expected_internal_state + + # When the measure directory does not exist, the C++ version catches it + assert not my_measures_dir.is_dir() + r = measure_manager_client.post("/set", json={"my_measures_dir": str(my_measures_dir)}) + if measure_manager_client.is_classic: + assert r.status_code == 200 + assert not r.json() + expected_internal_state["my_measures_dir"] = my_measures_dir.as_posix() + assert measure_manager_client.internal_state() == expected_internal_state + else: + assert r.status_code == 400 + assert "is a not a valid directory" in r.text + assert measure_manager_client.internal_state() == expected_internal_state + + my_measures_dir.mkdir(parents=True) + r = measure_manager_client.post("/set", json={"my_measures_dir": str(my_measures_dir)}) r.raise_for_status() expected_internal_state["my_measures_dir"] = my_measures_dir.as_posix() @@ -174,12 +239,16 @@ def test_set_measures_dir(measure_manager_client, expected_internal_state): # tmp_path is a built-in pytest fixture hich will provide a temporary directory unique to the test invocation, # created in the base temporary directory. +# @pytest.mark.skipif(measure_manager_client.is_classic, reason="Classic CLI does not have the POST /get_model") def test_get_model( measure_manager_client: MeasureManagerClient, expected_internal_state: Dict[str, Any], tmp_path: Path, osclipath: Path, ): + if measure_manager_client.is_classic: + pytest.skip("Classic CLI does not have the POST /get_model") + osm_path = tmp_path / "model.osm" osm_path2 = tmp_path / "model2.osm" @@ -191,7 +260,7 @@ def test_get_model( r.raise_for_status() assert r.json() == f"OK, loaded model with checksum {model_checksum}" - expected_internal_state["osm"].append( + expected_internal_state["osms"].append( { "checksum": model_checksum, "osm_path": osm_path.as_posix(), @@ -203,7 +272,7 @@ def test_get_model( r.raise_for_status() assert r.json() == f"OK, loaded model with checksum {model_checksum2}" - expected_internal_state["osm"].append( + expected_internal_state["osms"].append( { "checksum": model_checksum2, "osm_path": osm_path2.as_posix(), @@ -223,7 +292,7 @@ def test_get_model( r = measure_manager_client.post("/get_model", json={"osm_path": str(osm_path)}) r.raise_for_status() assert r.json() == f"OK, loaded model with checksum {model_checksum}" - expected_internal_state["osm"][0] = { + expected_internal_state["osms"][0] = { "checksum": model_checksum, "osm_path": osm_path.as_posix(), } @@ -234,11 +303,19 @@ def test_get_model( def test_download_bcl_measures(measure_manager_client: MeasureManagerClient, expected_internal_state: Dict[str, Any]): r = measure_manager_client.post(url="/download_bcl_measure", json={"": ""}) assert r.status_code == 400 - assert r.json() == "Missing the uid in the post data" + if measure_manager_client.is_classic: + # {"backtrace": [webcrick...], "error": "Missing required argument 'uid'} + assert "Missing required argument 'uid'" in r.text + else: + assert r.json() == "Missing the uid in the post data" r = measure_manager_client.post(url=f"/download_bcl_measure", json={"uid": "baduuid"}) assert r.status_code == 400 - assert r.json() == "Cannot find measure with uid='baduuid'" + if measure_manager_client.is_classic: + # {"backtrace": [webcrick...], "error": "Failed to download measure 'baduuid'"} + assert "Failed to download measure 'baduuid'" in r.text + else: + assert r.json() == "Cannot find measure with uid='baduuid'" measure_dir = BCL_DIR / TEST_BCL_MEASURE_UID if measure_dir.exists(): @@ -255,6 +332,8 @@ def test_download_bcl_measures(measure_manager_client: MeasureManagerClient, exp r = measure_manager_client.post(url="/download_bcl_measure", json={"uid": TEST_BCL_MEASURE_UID}) r.raise_for_status() measure_data = r.json() + if measure_manager_client.is_classic: + measure_data = measure_data[0] assert "name" in measure_data assert "display_name" in measure_data assert "version_id" in measure_data @@ -273,7 +352,10 @@ def test_download_bcl_measures(measure_manager_client: MeasureManagerClient, exp assert n_ori + 1 == n_new -def test_update_measures(measure_manager_client: MeasureManagerClient, expected_internal_state: Dict[str, Any]): +def test_update_measures(measure_manager_client: MeasureManagerClient, expected_internal_state: Dict[str, Any], + tmp_path: Path): + measure_manager_client.prime_a_new_my_measures_dir_with_a_single_measure(my_measures_dir=tmp_path / 'Measures') + r = measure_manager_client.post(url="/update_measures") r.raise_for_status() @@ -357,6 +439,8 @@ def test_compute_arguments( osclipath: Path, ): is_ruby = measure_language == "Ruby" + if measure_manager_client.is_classic and not is_ruby: + pytest.skip("Python not available for the classic CLI") measure_dir = tmp_path / f"new_{measure_language}_measure" assert not measure_dir.is_dir() readme_out_path = measure_dir / "README.md" @@ -410,9 +494,9 @@ def test_compute_arguments( assert len(arguments) == 2 construction_arg = next(x for x in arguments if x["name"] == "construction") assert "choice_display_names" in construction_arg - assert "choices_values" in construction_arg + assert "choice_values" in construction_arg assert len(construction_arg["choice_display_names"]) > 5 - assert len(construction_arg["choice_display_names"]) == len(construction_arg["choices_values"]) + assert len(construction_arg["choice_display_names"]) == len(construction_arg["choice_values"]) if is_ruby: assert readme_out_path.exists() @@ -437,7 +521,12 @@ def test_create_measure( } r = measure_manager_client.post(url="/create_measure", json=data) assert not r.ok - assert r.json() == "The 'measure_dir' (string) must be in the post data." + if measure_manager_client.is_classic: + # A really bad error message, because it fails in expand_path since measure_dir is nil + assert "expand_path" in r.json()["backtrace"] + assert "no implicit conversion of nil into String" in r.json()["error"] + else: + assert r.json() == "The 'measure_dir' (string) must be in the post data." measure_dir = tmp_path / "new_measure" data["measure_dir"] = measure_dir.as_posix() @@ -446,18 +535,24 @@ def test_create_measure( # The enums are throwy! Make sure we handle that gracefully r = measure_manager_client.post(url="/create_measure", json=data) assert not r.ok - assert ( - r.json() - == "Couldn't convert 'BadMeasureType' to a MeasureType: Unknown OpenStudio Enum Value 'BADMEASURETYPE' for Enum MeasureType" - ) + if measure_manager_client.is_classic: + assert r.json()["error"] == "Unknown OpenStudio Enum Value 'BADMEASURETYPE' for Enum MeasureType" + else: + assert ( + r.json() + == "Couldn't convert 'BadMeasureType' to a MeasureType: Unknown OpenStudio Enum Value 'BADMEASURETYPE' for Enum MeasureType" + ) data["measure_type"] = "ModelMeasure" r = measure_manager_client.post(url="/create_measure", json=data) assert not r.ok - assert ( - r.json() - == "Couldn't convert 'BadMeasureLanguage' to a MeasureLanguage: Unknown OpenStudio Enum Value 'BADMEASURELANGUAGE' for Enum MeasureLanguage" - ) + if measure_manager_client.is_classic: + assert r.json()["error"] == "Unknown OpenStudio Enum Value 'BADMEASURELANGUAGE' for Enum MeasureLanguage" + else: + assert ( + r.json() + == "Couldn't convert 'BadMeasureLanguage' to a MeasureLanguage: Unknown OpenStudio Enum Value 'BADMEASURELANGUAGE' for Enum MeasureLanguage" + ) data["measure_language"] = "Python" r = measure_manager_client.post("/create_measure", json=data) @@ -483,7 +578,10 @@ def test_create_measure( # Should fail gracefully when directory exists already r = measure_manager_client.post("/create_measure", json=data) assert not r.ok - assert "The directory already exists" in r.json() + if measure_manager_client.is_classic: + assert "exists but is not an empty directory" in r.json()["error"] + else: + assert "The directory already exists" in r.json() def test_duplicate_measure(