Skip to content

Commit

Permalink
Client for the enRoute Chouette Valid API (#4106)
Browse files Browse the repository at this point in the history
* Client for the Enroute Chouette Valid API

* Fix enRoute spelling
  • Loading branch information
ptitfred authored Aug 1, 2024
1 parent a146d15 commit 2d0b082
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 2 deletions.
72 changes: 72 additions & 0 deletions apps/transport/lib/validators/enroute_chouette_valid_client.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
defmodule Transport.EnRouteChouetteValidClient.Wrapper do
@moduledoc """
A client for the enRoute Chouette Valid API.
Documentation: https://documenter.getpostman.com/view/9950294/2sA3e2gVEE
"""

@callback create_a_validation(Path.t()) :: binary()
@callback get_a_validation(binary()) ::
:pending | {:successful, binary()} | :warning | :failed | :unexpected_validation_status
@callback get_messages(binary()) :: {binary(), map()}

def impl, do: Application.get_env(:transport, :enroute_validator_client)
end

defmodule Transport.EnRouteChouetteValidClient do
@moduledoc """
Implementation of the enRoute Chouette Valid API client.
"""
@behaviour Transport.EnRouteChouetteValidClient.Wrapper

@base_url "https://chouette-valid.enroute.mobi/api/validations"

@impl Transport.EnRouteChouetteValidClient.Wrapper
def create_a_validation(filepath) do
form =
{:multipart,
[
{"validation[rule_set]", "french"},
make_file_part("validation[file]", filepath)
]}

%HTTPoison.Response{status_code: 201, body: body} = http_client().post!(@base_url, form, auth_headers())
body |> Jason.decode!() |> Map.fetch!("id")
end

@impl Transport.EnRouteChouetteValidClient.Wrapper
def get_a_validation(validation_id) do
url = validation_url(validation_id)

%HTTPoison.Response{status_code: 200, body: body} = http_client().get!(url, auth_headers())

case body |> Jason.decode!() |> Map.fetch!("user_status") do
"pending" -> :pending
"successful" -> {:successful, url}
"warning" -> :warning
"failed" -> :failed
_ -> :unexpected_validation_status
end
end

@impl Transport.EnRouteChouetteValidClient.Wrapper
def get_messages(validation_id) do
url = Path.join([validation_url(validation_id), "messages"])

%HTTPoison.Response{status_code: 200, body: body} = http_client().get!(url, auth_headers())
{url, body |> Jason.decode!()}
end

defp make_file_part(field_name, filepath) do
{field_name, filepath, {"form-data", [{:name, "file"}, {:filename, Path.basename(filepath)}]}, []}
end

defp validation_url(validation_id) do
Path.join([@base_url, validation_id])
end

defp http_client, do: Transport.Shared.Wrapper.HTTPoison.impl()

defp auth_headers do
[{"authorization", "Token token=#{Application.fetch_env!(:transport, :enroute_validation_token)}"}]
end
end
1 change: 1 addition & 0 deletions apps/transport/test/support/mocks.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ Mox.defmock(Hasher.Mock, for: Hasher.Wrapper)
Mox.defmock(Transport.ValidatorsSelection.Mock, for: Transport.ValidatorsSelection)
Mox.defmock(Transport.SIRIQueryGenerator.Mock, for: Transport.SIRIQueryGenerator.Behaviour)
Mox.defmock(Transport.Unzip.S3.Mock, for: Transport.Unzip.S3.Behaviour)
Mox.defmock(Transport.EnRouteChouetteValidClient.Mock, for: Transport.EnRouteChouetteValidClient.Wrapper)
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
defmodule Transport.EnRouteChouetteValidClientTest do
use ExUnit.Case, async: true
import Mox

alias Transport.EnRouteChouetteValidClient

doctest Transport.EnRouteChouetteValidClient, import: true

setup :verify_on_exit!

@expected_headers [{"authorization", "Token token=fake_enroute_token"}]

test "create a validation" do
response_body =
"""
{
"id": "d8e2b6c2-b1e5-4890-84d4-9b761a445882",
"rule_set": "french",
"user_status": "pending",
"created_at": "2024-07-05T14:41:19.933Z",
"updated_at": "2024-07-05T14:41:19.933Z"
}
"""

url = "https://chouette-valid.enroute.mobi/api/validations"

tmp_file = System.tmp_dir!() |> Path.join("enroute_validation_netex_#{Ecto.UUID.generate()}")

expect(Transport.HTTPoison.Mock, :post!, fn ^url, {:multipart, parts}, headers ->
assert @expected_headers == headers

assert [
{"validation[rule_set]", "french"},
{"validation[file]", tmp_file, {"form-data", [{:name, "file"}, {:filename, Path.basename(tmp_file)}]},
[]}
] == parts

%HTTPoison.Response{status_code: 201, body: response_body}
end)

assert "d8e2b6c2-b1e5-4890-84d4-9b761a445882" == EnRouteChouetteValidClient.create_a_validation(tmp_file)
end

describe "get a validation" do
test "pending" do
validation_id = "d8e2b6c2-b1e5-4890-84d4-9b761a445882"

response_body =
"""
{
"id": "#{validation_id}",
"rule_set": "french",
"user_status": "pending",
"started_at": "2024-07-05T14:41:20.680Z",
"created_at": "2024-07-05T14:41:19.933Z",
"updated_at": "2024-07-05T14:41:19.933Z"
}
"""

url = "https://chouette-valid.enroute.mobi/api/validations/#{validation_id}"

expect(Transport.HTTPoison.Mock, :get!, fn ^url, headers ->
assert @expected_headers == headers
%HTTPoison.Response{status_code: 200, body: response_body}
end)

assert :pending == EnRouteChouetteValidClient.get_a_validation(validation_id)
end

test "successful" do
validation_id = "d8e2b6c2-b1e5-4890-84d4-9b761a445882"

response_body =
"""
{
"id": "#{validation_id}",
"rule_set": "french",
"user_status": "successful",
"started_at": "2024-07-05T14:41:20.680Z",
"ended_at": "2024-07-05T14:41:20.685Z",
"created_at": "2024-07-05T14:41:19.933Z",
"updated_at": "2024-07-05T14:41:19.933Z"
}
"""

url = "https://chouette-valid.enroute.mobi/api/validations/#{validation_id}"

expect(Transport.HTTPoison.Mock, :get!, fn ^url, headers ->
assert @expected_headers == headers
%HTTPoison.Response{status_code: 200, body: response_body}
end)

assert {:successful, url} == EnRouteChouetteValidClient.get_a_validation(validation_id)
end

test "warning" do
validation_id = "d8e2b6c2-b1e5-4890-84d4-9b761a445882"

response_body =
"""
{
"id": "#{validation_id}",
"rule_set": "french",
"user_status": "warning",
"started_at": "2024-07-05T14:41:20.680Z",
"ended_at": "2024-07-05T14:41:20.685Z",
"created_at": "2024-07-05T14:41:19.933Z",
"updated_at": "2024-07-05T14:41:19.933Z"
}
"""

url = "https://chouette-valid.enroute.mobi/api/validations/#{validation_id}"

expect(Transport.HTTPoison.Mock, :get!, fn ^url, headers ->
assert @expected_headers == headers
%HTTPoison.Response{status_code: 200, body: response_body}
end)

assert :warning == EnRouteChouetteValidClient.get_a_validation(validation_id)
end

test "failed" do
validation_id = "d8e2b6c2-b1e5-4890-84d4-9b761a445882"

response_body =
"""
{
"id": "#{validation_id}",
"rule_set": "french",
"user_status": "failed",
"started_at": "2024-07-05T14:41:20.680Z",
"ended_at": "2024-07-05T14:41:20.685Z",
"created_at": "2024-07-05T14:41:19.933Z",
"updated_at": "2024-07-05T14:41:19.933Z"
}
"""

url = "https://chouette-valid.enroute.mobi/api/validations/#{validation_id}"

expect(Transport.HTTPoison.Mock, :get!, fn ^url, headers ->
assert @expected_headers == headers
%HTTPoison.Response{status_code: 200, body: response_body}
end)

assert :failed == EnRouteChouetteValidClient.get_a_validation(validation_id)
end
end

test "get messages" do
response_body =
"""
[
{
"code": "uic-operating-period",
"message": "Resource 23504000009 hasn't expected class but Netex::OperatingPeriod",
"resource": {
"id": "23504000009",
"line": 665,
"class": "OperatingPeriod",
"column": 1,
"filename": "RESOURCE.xml"
},
"criticity": "error"
},
{
"code": "valid-day-bits",
"message": "Mandatory attribute valid_day_bits not found",
"resource": {
"id": "23504000057",
"line": 641,
"class": "OperatingPeriod",
"column": 1,
"filename": "RESOURCE.xml"
},
"criticity": "error"
},
{
"code": "frame-arret-resources",
"message": "Tag frame_id doesn't match ''",
"resource": {
"id": "5030",
"line": 424,
"class": "Quay",
"column": 5
},
"criticity": "error"
}
]
"""

validation_id = Ecto.UUID.generate()
url = "https://chouette-valid.enroute.mobi/api/validations/#{validation_id}/messages"

expect(Transport.HTTPoison.Mock, :get!, fn ^url, headers ->
assert @expected_headers == headers
%HTTPoison.Response{status_code: 200, body: response_body}
end)

{^url, messages} = EnRouteChouetteValidClient.get_messages(validation_id)
assert length(messages) == 3
end
end
4 changes: 3 additions & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ config :transport,
hasher_impl: Hasher,
validator_selection: Transport.ValidatorsSelection.Impl,
data_visualization: Transport.DataVisualization.Impl,
workflow_notifier: Transport.Jobs.Workflow.ObanNotifier
workflow_notifier: Transport.Jobs.Workflow.ObanNotifier,
enroute_validator_client: Transport.EnRouteChouetteValidClient

# Datagouv IDs for national databases created automatically.
# These are IDs used in staging, demo.data.gouv.fr
Expand Down Expand Up @@ -183,6 +184,7 @@ config :transport,
domain_name: System.get_env("DOMAIN_NAME", "transport.data.gouv.fr"),
export_secret_key: System.get_env("EXPORT_SECRET_KEY"),
enroute_token: System.get_env("ENROUTE_TOKEN"),
enroute_validation_token: System.get_env("ENROUTE_VALIDATION_TOKEN"),
max_import_concurrent_jobs: (System.get_env("MAX_IMPORT_CONCURRENT_JOBS") || "1") |> String.to_integer(),
nb_days_to_keep_validations: 60,
join_our_slack_link: "https://join.slack.com/t/transportdatagouvfr/shared_invite/zt-2n1n92ye-sdGQ9SeMh5BkgseaIzV8kA",
Expand Down
4 changes: 3 additions & 1 deletion config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ config :transport,
},
workflow_notifier: Transport.Jobs.Workflow.ProcessNotifier,
export_secret_key: "fake_export_secret_key",
enroute_token: "fake_enroute_token"
enroute_token: "fake_enroute_token",
enroute_validation_token: "fake_enroute_token",
enroute_validator_client: Transport.EnRouteChouetteValidClient.Mock

config :ex_aws,
cellar_organisation_id: "fake-cellar_organisation_id"
Expand Down

0 comments on commit 2d0b082

Please sign in to comment.