From 1345d008b6326f72ea2dded3c625a2d17ab3cd1a Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Tue, 16 Dec 2025 12:17:35 +0930 Subject: [PATCH 1/3] test(drivers): test the readme endpoint --- spec/controllers/drivers_spec.cr | 61 +++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/spec/controllers/drivers_spec.cr b/spec/controllers/drivers_spec.cr index 5f85a51c..40d6aeca 100644 --- a/spec/controllers/drivers_spec.cr +++ b/spec/controllers/drivers_spec.cr @@ -1,7 +1,7 @@ require "../helper" module PlaceOS::Api - describe Drivers do + describe Drivers, tags: "drivers" do describe "index", tags: "search" do Spec.test_base_index(klass: Model::Driver, controller_klass: Drivers) @@ -74,6 +74,65 @@ module PlaceOS::Api end end + describe "readme" do + it "returns the readme content for a driver" do + # Create a repository pointing to the real PlaceOS/drivers repo + repository = Model::Generator.repository(type: Model::Repository::Type::Driver) + repository.uri = "https://github.com/PlaceOS/drivers" + repository.save! + + # Create a driver with a real file path that has a readme + driver = Model::Driver.new( + name: "Auto Release", + role: Model::Driver::Role::Logic, + commit: "HEAD", + module_name: "AutoRelease", + file_name: "drivers/place/auto_release.cr", + ) + driver.repository = repository + driver.save! + + id = driver.id.as(String) + path = File.join(Drivers.base_route, id, "readme") + + result = client.get( + path: path, + headers: Spec::Authentication.headers, + ) + + result.success?.should be_true + result.body.should contain("Auto Release") + end + + it "returns 404 when readme does not exist" do + # Create a repository pointing to the real PlaceOS/drivers repo + repository = Model::Generator.repository(type: Model::Repository::Type::Driver) + repository.uri = "https://github.com/PlaceOS/drivers" + repository.save! + + # Create a driver with a file path that does NOT have a readme + driver = Model::Driver.new( + name: "No Readme Driver", + role: Model::Driver::Role::Logic, + commit: "HEAD", + module_name: "NoReadme", + file_name: "drivers/place/nonexistent_driver.cr", + ) + driver.repository = repository + driver.save! + + id = driver.id.as(String) + path = File.join(Drivers.base_route, id, "readme") + + result = client.get( + path: path, + headers: Spec::Authentication.headers, + ) + + result.status_code.should eq 404 + end + end + describe "scopes" do before_each do HttpMocks.core_compiled From eaea7213fbfe790ae4d8f18c061dc15b5ed5170e Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Tue, 16 Dec 2025 12:17:55 +0930 Subject: [PATCH 2/3] fix(drivers): test the readme endpoint --- src/placeos-rest-api/controllers/drivers.cr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/placeos-rest-api/controllers/drivers.cr b/src/placeos-rest-api/controllers/drivers.cr index c67099b3..dc01cbe7 100644 --- a/src/placeos-rest-api/controllers/drivers.cr +++ b/src/placeos-rest-api/controllers/drivers.cr @@ -91,9 +91,9 @@ module PlaceOS::Api driver_file_name = current_driver.file_name readme_path = driver_file_name.chomp(".cr") + "_readme.md" - # Create GitRepository instance - repository_path = File.join(Repositories.repository_dir, repository.folder_name) - git_repo = GitRepository.new(repository_path) + # Create GitRepository instance using the repository's remote URI + password = repository.decrypt_password if repository.password.presence + git_repo = GitRepository.new(repository.uri, repository.username, password) # Check if the readme file exists using the driver's commit files = git_repo.file_list(ref: current_driver.commit, path: readme_path) From bb52c26565e35a060697e1720a5bb545654e8619 Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Tue, 16 Dec 2025 12:26:01 +0930 Subject: [PATCH 3/3] refactor: fix ameba issues --- .ameba.yml | 2 + src/placeos-rest-api/controllers/alerts.cr | 1 - .../utilities/current-user.cr | 3 +- .../utilities/ms-token-exchange.cr | 38 +++++++++---------- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/.ameba.yml b/.ameba.yml index 6a3ef5a6..39924a3a 100644 --- a/.ameba.yml +++ b/.ameba.yml @@ -16,6 +16,8 @@ Naming/AccessorMethodName: Enabled: false Lint/SpecFilename: Enabled: false +Lint/UselessAssign: + Enabled: false Documentation/DocumentationAdmonition: Enabled: false diff --git a/src/placeos-rest-api/controllers/alerts.cr b/src/placeos-rest-api/controllers/alerts.cr index fc7ef9c0..0c1b6b7b 100644 --- a/src/placeos-rest-api/controllers/alerts.cr +++ b/src/placeos-rest-api/controllers/alerts.cr @@ -77,7 +77,6 @@ module PlaceOS::Api }) end - p! query query.sort(NAME_SORT_ASC) paginate_results(elastic, query) end diff --git a/src/placeos-rest-api/utilities/current-user.cr b/src/placeos-rest-api/utilities/current-user.cr index 67871a33..6dc5fcd6 100644 --- a/src/placeos-rest-api/utilities/current-user.cr +++ b/src/placeos-rest-api/utilities/current-user.cr @@ -14,6 +14,7 @@ module PlaceOS::Api # Parses, and validates JWT if present. # Throws Error::MissingBearer and JWT::Error. + # ameba:disable Metrics/CyclomaticComplexity def authorize! : ::PlaceOS::Model::UserJWT if token = @user_token return token @@ -42,7 +43,7 @@ module PlaceOS::Api begin # peek the token to determine type token_info = Utils::MSTokenExchange.peek_token_info(token) - if token_info.is_ms_token? + if token_info.ms_token? user = Utils::MSTokenExchange.obtain_place_user(token, token_info) raise "MS token could not be exchanged" unless user @current_user = user diff --git a/src/placeos-rest-api/utilities/ms-token-exchange.cr b/src/placeos-rest-api/utilities/ms-token-exchange.cr index 74c71ee0..ecf89f78 100644 --- a/src/placeos-rest-api/utilities/ms-token-exchange.cr +++ b/src/placeos-rest-api/utilities/ms-token-exchange.cr @@ -26,7 +26,7 @@ module PlaceOS::Api version : TokenVersion, kid : String? do # Basic heuristic to detect Microsoft Entra / Azure AD issuers - def is_ms_token? : Bool + def ms_token? : Bool iss_val = iss_host return false unless iss_val iss_val = iss_val.downcase @@ -119,14 +119,14 @@ module PlaceOS::Api # if not existing or refresh failed, get a token using this token and on behalf of # https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-on-behalf-of-flow#example - form = URI::Params.build do |form| - form.add "grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer" - form.add "client_id", oauth.client_id - form.add "client_secret", oauth.client_secret - form.add "assertion", token - form.add "scope", oauth.scope - form.add "requested_token_use", "on_behalf_of" - form.add "resource", "https://graph.microsoft.com/" + form = URI::Params.build do |builder| + builder.add "grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer" + builder.add "client_id", oauth.client_id + builder.add "client_secret", oauth.client_secret + builder.add "assertion", token + builder.add "scope", oauth.scope + builder.add "requested_token_use", "on_behalf_of" + builder.add "resource", "https://graph.microsoft.com/" end uri = token_info.token_endpoint @@ -163,23 +163,19 @@ module PlaceOS::Api # ---------- Audience Parsing ---------- def extract_aud_host(aud_raw : String) : String - begin - uri = URI.parse(aud_raw) - uri.host || aud_raw - rescue - aud_raw - end + uri = URI.parse(aud_raw) + uri.host || aud_raw + rescue + aud_raw end # ---------- Issuer Parsing ---------- def extract_issuer_host(iss_raw : String) : String? - begin - uri = URI.parse(iss_raw) - uri.host - rescue - nil - end + uri = URI.parse(iss_raw) + uri.host + rescue + nil end # ---------- Validation (JWKS) ----------