From 8e86e595589832943caee9a31884d4901fbb1b75 Mon Sep 17 00:00:00 2001 From: Cam Cook Date: Thu, 22 May 2025 13:38:09 -0400 Subject: [PATCH 1/3] chore: setup config --- config/config.exs | 12 ++++++++++++ config/dev.exs | 2 ++ config/prod.exs | 1 + config/runtime.exs | 1 + config/test.exs | 2 ++ 5 files changed, 18 insertions(+) create mode 100644 config/config.exs create mode 100644 config/dev.exs create mode 100644 config/prod.exs create mode 100644 config/runtime.exs create mode 100644 config/test.exs diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..40bbb8f --- /dev/null +++ b/config/config.exs @@ -0,0 +1,12 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Config module. +# +# This configuration file is loaded before any dependency and +# is restricted to this project. + +# General application configuration +import Config + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{config_env()}.exs" diff --git a/config/dev.exs b/config/dev.exs new file mode 100644 index 0000000..d4c174a --- /dev/null +++ b/config/dev.exs @@ -0,0 +1,2 @@ +import Config + diff --git a/config/prod.exs b/config/prod.exs new file mode 100644 index 0000000..becde76 --- /dev/null +++ b/config/prod.exs @@ -0,0 +1 @@ +import Config diff --git a/config/runtime.exs b/config/runtime.exs new file mode 100644 index 0000000..becde76 --- /dev/null +++ b/config/runtime.exs @@ -0,0 +1 @@ +import Config diff --git a/config/test.exs b/config/test.exs new file mode 100644 index 0000000..d4c174a --- /dev/null +++ b/config/test.exs @@ -0,0 +1,2 @@ +import Config + From f1b1ff68fbb409bc2ebde900f20fd8ea8c29f949 Mon Sep 17 00:00:00 2001 From: Cam Cook Date: Thu, 22 May 2025 16:53:59 -0400 Subject: [PATCH 2/3] feat(repos): get all repos --- config/dev.exs | 1 - config/test.exs | 1 - lib/aura/model/common.ex | 42 ++++++ lib/aura/model/hex_repo.ex | 24 ++++ lib/aura/repos.ex | 16 +++ lib/aura/util/cache.ex | 3 + lib/aura/util/requester.ex | 128 +++++++++++++++++++ mix.exs | 11 +- mix.lock | 9 ++ test/aura/model/aura/model/hex_repo_test.exs | 16 +++ test/aura/repos_test.exs | 11 ++ test/support/factories/hex_repo_factory.ex | 22 ++++ test/support/factory.ex | 5 + test/test_helper.exs | 8 +- 14 files changed, 292 insertions(+), 5 deletions(-) create mode 100644 lib/aura/model/common.ex create mode 100644 lib/aura/model/hex_repo.ex create mode 100644 lib/aura/repos.ex create mode 100644 lib/aura/util/cache.ex create mode 100644 lib/aura/util/requester.ex create mode 100644 test/aura/model/aura/model/hex_repo_test.exs create mode 100644 test/aura/repos_test.exs create mode 100644 test/support/factories/hex_repo_factory.ex create mode 100644 test/support/factory.ex diff --git a/config/dev.exs b/config/dev.exs index d4c174a..becde76 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -1,2 +1 @@ import Config - diff --git a/config/test.exs b/config/test.exs index d4c174a..becde76 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,2 +1 @@ import Config - diff --git a/lib/aura/model/common.ex b/lib/aura/model/common.ex new file mode 100644 index 0000000..82e9f1a --- /dev/null +++ b/lib/aura/model/common.ex @@ -0,0 +1,42 @@ +defmodule Aura.Model.Common do + @moduledoc false + + @spec prepare(m :: map()) :: map() + def prepare(m) when is_map(m) do + prepare_keys(m) + |> prepare_values() + |> Enum.reject(fn {_k, v} -> v == nil end) + |> Enum.map(fn {key, val} -> + {key, serialize(key, val)} + end) + end + + defp prepare_keys(m) do + m + |> snake_case_keys() + |> atomize_keys() + end + + def prepare_values(m) do + Enum.map(m, fn {key, val} -> + if val && String.ends_with?("#{key}", "_at"), + do: {key, DateTimeParser.parse_datetime!(val)}, + else: {key, val} + end) + end + + defp snake_case_keys(m) do + Enum.map(m, fn {key, val} -> + {ProperCase.snake_case(key), val} + end) + end + + defp atomize_keys(m) do + Enum.map(m, fn {key, val} -> + key = String.to_atom(key) + {key, val} + end) + end + + defp serialize(_k, v), do: v +end diff --git a/lib/aura/model/hex_repo.ex b/lib/aura/model/hex_repo.ex new file mode 100644 index 0000000..7cc26c5 --- /dev/null +++ b/lib/aura/model/hex_repo.ex @@ -0,0 +1,24 @@ +defmodule Aura.Model.HexRepo do + @moduledoc false + + import Aura.Model.Common + + alias Aura.Model.HexRepo + + defstruct [ + :name, + :public, + :active, + :billing_active, + :inserted_at, + :updated_at + ] + + def build(m) when is_map(m) do + fields = + m + |> prepare() + + struct(HexRepo, fields) + end +end diff --git a/lib/aura/repos.ex b/lib/aura/repos.ex new file mode 100644 index 0000000..6260974 --- /dev/null +++ b/lib/aura/repos.ex @@ -0,0 +1,16 @@ +defmodule Aura.Repos do + @moduledoc false + + alias Aura.Model.HexRepo + alias Aura.Requester + + def get_all_repos do + with {:ok, %{body: body}} <- Requester.request(:get, "/repos") do + body + |> Enum.map(fn + repo -> + HexRepo.build(repo) + end) + end + end +end diff --git a/lib/aura/util/cache.ex b/lib/aura/util/cache.ex new file mode 100644 index 0000000..354f033 --- /dev/null +++ b/lib/aura/util/cache.ex @@ -0,0 +1,3 @@ +defmodule Aura.Cache do + @moduledoc false +end diff --git a/lib/aura/util/requester.ex b/lib/aura/util/requester.ex new file mode 100644 index 0000000..3cde0e1 --- /dev/null +++ b/lib/aura/util/requester.ex @@ -0,0 +1,128 @@ +# SPDX-License-Identifier: Apache-2.0 +defmodule Aura.Requester do + @moduledoc false + + require Logger + + @base_url "https://hex.pm/api" + + def request(method, path, opts \\ []) do + qparams = opts[:qparams] + is_retry = opts[:is_retry] + + opts = + opts + |> Keyword.delete(:qparams) + |> Keyword.delete(:is_retry) + |> Keyword.put(:headers, [user_agent_header()]) + + path = + @base_url + |> Path.join(path) + |> handle_qparams(qparams) + + method + |> make_request(path, opts) + |> case do + {:ok, + %Req.Response{ + status: 200, + body: _body, + headers: headers + }} = resp -> + respect_limits(headers) + resp + + {:ok, %Req.Response{status: 204, body: _body, headers: headers}} = resp -> + respect_limits(headers) + resp + + {:ok, %Req.Response{status: 429, headers: headers}} -> + if is_retry do + {:error, "Rate limit exceeded"} + else + respect_limits(headers) + new_opts = Keyword.put(opts, :is_retry, true) + request(method, path, new_opts) + end + + other -> + {:error, other} + end + end + + def get(path, opts \\ []), do: request(:get, path, opts) + + def post(path, opts \\ []), do: request(:post, path, opts) + + def put(path, opts \\ []), do: request(:put, path, opts) + + def patch(path, opts \\ []), do: request(:patch, path, opts) + + def delete(path, opts \\ []), do: request(:delete, path, opts) + + defp user_agent_header do + config = Mix.Project.config() + app = config[:app] || :unknown + version = config[:version] || "0.0.0" + e_version = System.version() + mix_env = Mix.env() + user_agent = "#{app}/#{version} (Elixir/#{e_version}) (OTP/#{otp_version()})(#{mix_env})" + {"User-Agent", user_agent} + end + + defp otp_version do + major = :erlang.system_info(:otp_release) |> List.to_string() + vsn_file = Path.join([:code.root_dir(), "releases", major, "OTP_VERSION"]) + + try do + {:ok, contents} = File.read(vsn_file) + String.split(contents, "\n", trim: true) + else + [full] -> full + _ -> major + catch + :error, _ -> major + end + end + + defp handle_qparams(url, nil), do: url + + defp handle_qparams(url, qparams) do + qparams = + qparams + |> Enum.filter(fn {_k, v} -> v end) + |> URI.encode_query() + + url + |> URI.parse() + |> Map.put(:query, qparams) + |> URI.to_string() + end + + defp make_request(:get, path, opts), do: Req.get(path, opts) + defp make_request(:post, path, opts), do: Req.post(path, opts) + defp make_request(:put, path, opts), do: Req.put(path, opts) + defp make_request(:delete, path, opts), do: Req.delete(path, opts) + defp make_request(:patch, path, opts), do: Req.patch(path, opts) + + defp respect_limits(%{"x-ratelimit-remaining" => ["0"]} = headers) do + [reset] = headers["x-ratelimit-reset"] || ["0"] + + unix_reset = + reset + |> String.to_integer() + |> DateTime.from_unix!() + + wait_ms = + unix_reset + |> DateTime.diff(DateTime.now!("Etc/UTC"), :millisecond) + |> max(0) + + Logger.warning("Hit a rate limit, waiting #{wait_ms} milliseconds and retrying.") + + :timer.sleep(wait_ms) + end + + defp respect_limits(_), do: :ok +end diff --git a/mix.exs b/mix.exs index 38fe1d8..938e049 100644 --- a/mix.exs +++ b/mix.exs @@ -1,4 +1,4 @@ -defmodule ExFTP.MixProject do +defmodule Aura.MixProject do use Mix.Project @source_url "https://github.com/camatcode/aura" @@ -11,6 +11,7 @@ defmodule ExFTP.MixProject do elixir: "~> 1.18", start_permanent: Mix.env() == :prod, deps: deps(), + elixirc_paths: elixirc_paths(Mix.env()), test_coverage: [tool: ExCoveralls], preferred_cli_env: [ coveralls: :test, @@ -83,7 +84,13 @@ defmodule ExFTP.MixProject do {:ex_machina, "~> 2.8.0", only: :test}, {:faker, "~> 0.18.0", only: :test}, {:junit_formatter, "~> 3.1", only: [:test]}, - {:req, "~> 0.5.10"} + {:req, "~> 0.5.10"}, + {:cachex, "~> 4.0"}, + {:proper_case, "~> 1.3"}, + {:date_time_parser, "~> 1.2.0"} ] end + + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] end diff --git a/mix.lock b/mix.lock index ae29658..2635530 100644 --- a/mix.lock +++ b/mix.lock @@ -1,8 +1,12 @@ %{ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, + "cachex": {:hex, :cachex, "4.1.0", "f9a9123feac30df2b75260c9a74903c0bd7ec54f345f5f6f60c7830b81ead43a", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:ex_hash_ring, "~> 6.0", [hex: :ex_hash_ring, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "77f9417a5549e424bdbfeea3f8060841384c39c0b5f563a8f62f1f2c2b6d2345"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, + "date_time_parser": {:hex, :date_time_parser, "1.2.0", "3d5a816b91967f51e0f94dcb16a34b2cb780f22cd48931779e81d72f7d3eadb1", [:mix], [{:kday, "~> 1.0", [hex: :kday, repo: "hexpm", optional: false]}], "hexpm", "0cf09ada9f42c0b3bfba02dc0ea2e4b4d2f543d9d2bf99b831a29e6b4a4160e5"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"}, "ex_doc": {:hex, :ex_doc, "0.38.1", "bae0a0bd5b5925b1caef4987e3470902d072d03347114ffe03a55dbe206dd4c2", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "754636236d191b895e1e4de2ebb504c057fe1995fdfdd92e9d75c4b05633008b"}, + "ex_hash_ring": {:hex, :ex_hash_ring, "6.0.4", "bef9d2d796afbbe25ab5b5a7ed746e06b99c76604f558113c273466d52fa6d6b", [:mix], [], "hexpm", "89adabf31f7d3dfaa36802ce598ce918e9b5b33bae8909ac1a4d052e1e567d18"}, "ex_license": {:hex, :ex_license, "0.1.1", "bbdaba704f861894da3ae80e4399984379f19de1cca4ecf7d799616c832b8821", [:mix], [], "hexpm", "dd95aaaba0c9c6f0e9be2db3e9fac0bf69784557e04f700f94e90629f5e24e1d"}, "ex_machina": {:hex, :ex_machina, "2.8.0", "a0e847b5712065055ec3255840e2c78ef9366634d62390839d4880483be38abe", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "79fe1a9c64c0c1c1fab6c4fa5d871682cb90de5885320c187d117004627a7729"}, "excoveralls": {:hex, :excoveralls, "0.18.5", "e229d0a65982613332ec30f07940038fe451a2e5b29bce2a5022165f0c9b157e", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "523fe8a15603f86d64852aab2abe8ddbd78e68579c8525ae765facc5eae01562"}, @@ -11,7 +15,9 @@ "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"}, "junit_formatter": {:hex, :junit_formatter, "3.4.0", "d0e8db6c34dab6d3c4154c3b46b21540db1109ae709d6cf99ba7e7a2ce4b1ac2", [:mix], [], "hexpm", "bb36e2ae83f1ced6ab931c4ce51dd3dbef1ef61bb4932412e173b0cfa259dacd"}, + "kday": {:hex, :kday, "1.1.0", "64efac85279a12283eaaf3ad6f13001ca2dff943eda8c53288179775a8c057a0", [:mix], [{:ex_doc, "~> 0.21", [hex: :ex_doc, repo: "hexpm", optional: true]}], "hexpm", "69703055d63b8d5b260479266c78b0b3e66f7aecdd2022906cd9bf09892a266d"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, @@ -20,7 +26,10 @@ "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "proper_case": {:hex, :proper_case, "1.3.1", "5f51cabd2d422a45f374c6061b7379191d585b5154456b371432d0fa7cb1ffda", [:mix], [], "hexpm", "6cc715550cc1895e61608060bbe67aef0d7c9cf55d7ddb013c6d7073036811dd"}, "quokka": {:hex, :quokka, "2.6.0", "a34ff876968fcef20594e349bd7d9a940c0ba85830d0efd8416ee8c0de4c430d", [:mix], [{:credo, "~> 1.7", [hex: :credo, repo: "hexpm", optional: false]}], "hexpm", "52ea62131cef122e2daddbb3b9f61b14d04adcb39316fe0dae03ca65eeec270b"}, "req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"}, + "sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"}, } diff --git a/test/aura/model/aura/model/hex_repo_test.exs b/test/aura/model/aura/model/hex_repo_test.exs new file mode 100644 index 0000000..f5ae598 --- /dev/null +++ b/test/aura/model/aura/model/hex_repo_test.exs @@ -0,0 +1,16 @@ +defmodule Aura.Model.HexRepoTest do + use ExUnit.Case + + import Aura.Factory + + alias Aura.Model.HexRepo + + @moduletag :capture_log + + doctest HexRepo + + test "build_many" do + list = build_list(100, :hex_repo) + assert Enum.count(list) == 100 + end +end diff --git a/test/aura/repos_test.exs b/test/aura/repos_test.exs new file mode 100644 index 0000000..8d191c6 --- /dev/null +++ b/test/aura/repos_test.exs @@ -0,0 +1,11 @@ +defmodule Aura.ReposTest do + use ExUnit.Case + + alias Aura.Repos + + doctest Repos + + test "get_all_repos/0" do + assert [%{name: "hexpm"}] = Repos.get_all_repos() + end +end diff --git a/test/support/factories/hex_repo_factory.ex b/test/support/factories/hex_repo_factory.ex new file mode 100644 index 0000000..52af838 --- /dev/null +++ b/test/support/factories/hex_repo_factory.ex @@ -0,0 +1,22 @@ +defmodule Aura.Factory.HexRepoFactory do + @moduledoc false + + defmacro __using__(_opts) do + quote do + def hex_repo_factory(attrs) do + inserted_at = Faker.DateTime.backward(40) + + %Aura.Model.HexRepo{ + name: Faker.Internet.user_name(), + public: Enum.random([true, false]), + active: Enum.random([true, false]), + billing_active: Enum.random([true, false]), + inserted_at: inserted_at, + updated_at: Faker.DateTime.between(inserted_at, DateTime.now!("Etc/UTC")) + } + |> merge_attributes(attrs) + |> evaluate_lazy_attributes() + end + end + end +end diff --git a/test/support/factory.ex b/test/support/factory.ex new file mode 100644 index 0000000..cf8b135 --- /dev/null +++ b/test/support/factory.ex @@ -0,0 +1,5 @@ +defmodule Aura.Factory do + @moduledoc false + use ExMachina + use Aura.Factory.HexRepoFactory +end diff --git a/test/test_helper.exs b/test/test_helper.exs index 869559e..29d7f97 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1 +1,7 @@ -ExUnit.start() +ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter]) +{:ok, _} = Application.ensure_all_started(:ex_machina) +ExUnit.start(timeout: 2 * 60 * 1000) + +defmodule Helper do + @moduledoc false +end From 8d1145f1b3b077ea81e03178d6d99c2863c20651 Mon Sep 17 00:00:00 2001 From: Cam Cook Date: Thu, 22 May 2025 17:28:17 -0400 Subject: [PATCH 3/3] feat(repos): get repo --- lib/aura/repos.ex | 16 +++++++++++----- test/aura/repos_test.exs | 8 +++++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/aura/repos.ex b/lib/aura/repos.ex index 6260974..55da55a 100644 --- a/lib/aura/repos.ex +++ b/lib/aura/repos.ex @@ -6,11 +6,17 @@ defmodule Aura.Repos do def get_all_repos do with {:ok, %{body: body}} <- Requester.request(:get, "/repos") do - body - |> Enum.map(fn - repo -> - HexRepo.build(repo) - end) + results = + body + |> Enum.map(&HexRepo.build/1) + + {:ok, results} + end + end + + def get_repo(repo_name) when is_bitstring(repo_name) do + with {:ok, %{body: body}} <- Requester.request(:get, "/repos/#{repo_name}") do + {:ok, HexRepo.build(body)} end end end diff --git a/test/aura/repos_test.exs b/test/aura/repos_test.exs index 8d191c6..878edc4 100644 --- a/test/aura/repos_test.exs +++ b/test/aura/repos_test.exs @@ -6,6 +6,12 @@ defmodule Aura.ReposTest do doctest Repos test "get_all_repos/0" do - assert [%{name: "hexpm"}] = Repos.get_all_repos() + assert {:ok, [%{name: "hexpm"}]} = Repos.get_all_repos() + end + + test "get_repo/1" do + assert {:ok, [hex]} = Repos.get_all_repos() + assert {:ok, returned} = Repos.get_repo(hex.name) + assert returned.name == hex.name end end