diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c464bb5..2858083 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,6 +36,10 @@ jobs: issues: true issuesWoLabels: true stripGeneratorNotice: true + - name: Publish + run: mix hex.publish --replace --yes + env: + HEX_API_KEY: ${{ secrets.HEX_API_KEY }} - name: Save version uses: github-actions-x/commit@v2.9 with: @@ -47,7 +51,3 @@ jobs: files: mix.exs CHANGELOG.md name: Release Bot email: release-bot@bancolombia.com.co - - name: Publish - run: mix hex.publish --replace --yes - env: - HEX_API_KEY: ${{ secrets.HEX_API_KEY }} diff --git a/.tool-versions b/.tool-versions index 902e233..82cd22f 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -elixir 1.15.7-otp-24 -erlang 24.3.4.14 \ No newline at end of file +erlang 26.1.1 +elixir 1.15.6-otp-26 \ No newline at end of file diff --git a/lib/core/apply_template.ex b/lib/core/apply_template.ex index bca1985..e2d34df 100644 --- a/lib/core/apply_template.ex +++ b/lib/core/apply_template.ex @@ -34,7 +34,6 @@ defmodule ElixirStructureManager.Core.ApplyTemplate do defp resolve_behaviour(:asynceventhandler), do: EP.AsyncEventHandlers defp resolve_behaviour(:metrics), do: Config.Metrics - defp resolve_behaviour(:distillery), do: Config.Distillery defp resolve_behaviour(:sonar), do: Config.Sonar defp resolve_behaviour(_other) do diff --git a/lib/core/config/distillery.ex b/lib/core/config/distillery.ex deleted file mode 100644 index fd13334..0000000 --- a/lib/core/config/distillery.ex +++ /dev/null @@ -1,22 +0,0 @@ -defmodule Config.Distillery do - @moduledoc false - - def actions do - provider = """ - set config_providers: [{Distillery.Releases.Config.Providers.Elixir, ["${RELEASE_ROOT_DIR}/etc/config.exs"]}] # Use config file at runtime - """ - - %{ - transformations: [ - {:inject_dependency, ~s|{:distillery, "~> 2.1"}|}, - {:run_task, :install_deps}, - {:run_task, :distillery_init}, - {:insert_after, "rel/config.exs", provider, regex: ~r|"rel\/vm\.args"(\r?)\n|} - ] - } - end - - def tokens(_opts) do - [] - end -end diff --git a/lib/core/config/sonar.ex b/lib/core/config/sonar.ex index 911600b..6ec8786 100644 --- a/lib/core/config/sonar.ex +++ b/lib/core/config/sonar.ex @@ -16,8 +16,8 @@ defmodule Config.Sonar do "test/support/test_stubs.exs" => @base <> "test_stubs.exs" }, transformations: [ + {:inject_dependency, ~s|{:sobelow, "~> 0.13", only: :dev}|}, {:inject_dependency, ~s|{:credo_sonarqube, "~> 0.1"}|}, - {:inject_dependency, ~s|{:sobelow, "~> 0.11", only: :dev}|}, {:inject_dependency, ~s|{:ex_unit_sonarqube, "~> 0.1", only: :test}|}, {:append_end, ".gitignore", ignore}, {:run_task, :install_deps} diff --git a/lib/mix/tasks/ca.apply.config.ex b/lib/mix/tasks/ca.apply.config.ex index 40859db..8f4f305 100644 --- a/lib/mix/tasks/ca.apply.config.ex +++ b/lib/mix/tasks/ca.apply.config.ex @@ -5,12 +5,12 @@ defmodule Mix.Tasks.Ca.Apply.Config do Type param options: - * distillery + * sonar * metrics Examples: - $ mix ca.apply.config --type distillery - $ mix ca.apply.config -t distillery + $ mix ca.apply.config --type sonar + $ mix ca.apply.config -t metrics """ alias ElixirStructureManager.Core.ApplyTemplate diff --git a/lib/mix/tasks/ca.new.structure.ex b/lib/mix/tasks/ca.new.structure.ex index fc29564..f316537 100644 --- a/lib/mix/tasks/ca.new.structure.ex +++ b/lib/mix/tasks/ca.new.structure.ex @@ -12,8 +12,8 @@ defmodule Mix.Tasks.Ca.New.Structure do use Mix.Task @version Mix.Project.config()[:version] - @switches [metrics: :boolean, distillery: :boolean] - @aliases [m: :metrics, d: :distillery] + @switches [metrics: :boolean, sonar: :boolean] + @aliases [m: :metrics, s: :sonar] def run([]), do: run(["-h"]) @@ -39,8 +39,8 @@ defmodule Mix.Tasks.Ca.New.Structure do CommonCommands.install_deps(root_dir) - if opts[:distillery] do - CommonCommands.config_distillery(root_dir) + if opts[:sonar] do + CommonCommands.config_sonar(root_dir) end if opts[:metrics] do diff --git a/lib/mix/tasks/ca.release.ex b/lib/mix/tasks/ca.release.ex new file mode 100644 index 0000000..9044ff3 --- /dev/null +++ b/lib/mix/tasks/ca.release.ex @@ -0,0 +1,42 @@ +defmodule Mix.Tasks.Ca.Release do + @moduledoc """ + Mix release wrapper that moves compressed artifact to _build/release/artifact folder + + Examples: + $ mix ca.release + + It generates the following files: + * _build/release/artifact/.tar.gz + """ + + alias ElixirStructureManager.Utils.{FileGenerator, TokenHelper} + alias Mix.Tasks.Ca.BaseTask + + use BaseTask, + name: "ca.release", + description: + "Mix release wrapper that moves compressed artifact to _build/release/artifact folder", + switches: [], + aliases: [] + + def execute(_any) do + Mix.shell().info([:green, "* Generating release artifact"]) + + args = %{ + folders: [ + "_build/prod/rel/{app_snake}/releases/RELEASES", + "_build/release/artifact" + ], + transformations: [ + {:cmd, "mix release --overwrite"}, + {:cmd, + "mv _build/prod/{app_snake}-{version}.tar.gz _build/release/artifact/{app_snake}.tar.gz"}, + {:cmd, "ls -lR _build/release"} + ] + } + + FileGenerator.execute_actions(args, TokenHelper.default_tokens()) + + Mix.shell().info([:green, "* Artifact generated"]) + end +end diff --git a/lib/mix/tasks/ca.sobelow.sonar.ex b/lib/mix/tasks/ca.sobelow.sonar.ex new file mode 100644 index 0000000..03f5383 --- /dev/null +++ b/lib/mix/tasks/ca.sobelow.sonar.ex @@ -0,0 +1,40 @@ +defmodule Mix.Tasks.Ca.Sobelow.Sonar do + @moduledoc """ + Translates the sobelow json report to sonar issues + + Examples: + $ mix ca.sobelow.sonar -i sobelow.json -o sobelow_sonarqube.json + """ + + alias ElixirStructureManager.Reports.Sobelow + alias Mix.Tasks.Ca.BaseTask + + use BaseTask, + name: "ca.sobelow.sonar", + description: "Translates the sobelow json report to sonar issues", + switches: [input: :string, output: :string], + aliases: [i: :input, o: :output] + + def execute({opts, []}) do + Mix.shell().info([:green, "* Translating sobelow report"]) + + input = Keyword.get(opts, :input, "sobelow.json") + output = Keyword.get(opts, :output, "sobelow_sonarqube.json") + + sonar_base_folder = Application.get_env(:elixir_structure_manager, :sonar_base_folder, "") + + File.exists?(input) || Mix.shell().error([:red, "* Input file not found"]) + + report = + File.read!(input) + |> Poison.decode!() + + sonar_report = Sobelow.translate(report, sonar_base_folder) + json = Poison.encode!(sonar_report, %{pretty: true}) + File.write!(output, json) + + Mix.shell().info([:green, "* Sonarqube report generated"]) + end + + def execute(_any), do: run(["-h"]) +end diff --git a/lib/mix/tasks/ca.test.ex b/lib/mix/tasks/ca.test.ex new file mode 100644 index 0000000..2a62fff --- /dev/null +++ b/lib/mix/tasks/ca.test.ex @@ -0,0 +1,51 @@ +defmodule Mix.Tasks.Ca.Test do + @moduledoc """ + Run common static code analysis tools and tests for the project + + Examples: + $ mix ca.test + + It generates the following files: + * _build/release/credo_sonarqube.json + * _build/release/excoveralls.xml + * _build/release/generic_test_execution_sonarqube.xml + * _build/release/sobelow.json + * _build/release/sobelow_sonarqube.json + * _build/release/test-junit-report.xml + """ + + alias ElixirStructureManager.Utils.{FileGenerator, TokenHelper} + alias Mix.Tasks.Ca.BaseTask + + use BaseTask, + name: "ca.test", + description: "Run common static code analysis tools and tests for the project", + switches: [], + aliases: [] + + def execute(_any) do + Mix.shell().info([:green, "* Executing analysis and tests"]) + + sonar_base_folder = Application.get_env(:elixir_structure_manager, :sonar_base_folder, "") + + args = %{ + folders: [ + "_build/release" + ], + transformations: [ + {:cmd, + "mix credo --sonarqube-base-folder {sonar_base_folder} --sonarqube-file _build/release/credo_sonarqube.json --mute-exit-status"}, + {:cmd, "mix sobelow -f json --out _build/release/sobelow.json"}, + {:cmd, + "mix ca.sobelow.sonar -i _build/release/sobelow.json -o _build/release/sobelow_sonarqube.json"}, + {:cmd, "mix coveralls.xml"}, + {:cmd, + "mv generic_test_execution_sonarqube.xml _build/release/generic_test_execution_sonarqube.xml"} + ] + } + + FileGenerator.execute_actions(args, TokenHelper.add("sonar_base_folder", sonar_base_folder)) + + Mix.shell().info([:green, "* Analysis executed"]) + end +end diff --git a/lib/reports/sobelow.ex b/lib/reports/sobelow.ex new file mode 100644 index 0000000..e212887 --- /dev/null +++ b/lib/reports/sobelow.ex @@ -0,0 +1,61 @@ +defmodule ElixirStructureManager.Reports.Sobelow do + @moduledoc """ + Sobelow report generator + """ + + def translate( + %{ + "findings" => %{ + "high_confidence" => highs, + "medium_confidence" => meds, + "low_confidence" => lows + }, + "sobelow_version" => vsn + }, + sonar_base_folder + ) do + issues = + Enum.map(highs, &format(&1, :high, vsn, sonar_base_folder)) + |> Enum.concat(Enum.map(meds, &format(&1, :medium, vsn, sonar_base_folder))) + |> Enum.concat(Enum.map(lows, &format(&1, :low, vsn, sonar_base_folder))) + + %{issues: issues} + end + + defp format( + %{"type" => type, "file" => file, "line" => line} = finding, + confidence, + vsn, + prefix + ) do + variable = Map.get(finding, "variable", "") + [mod_id, _] = String.split(type, ":", parts: 2) + # Code.ensure_loaded(Sobelow) + rule = Sobelow.get_mod(mod_id) + + location = %{ + filePath: "#{prefix}#{file}", + message: "#{type} #{variable} \n Help: #{rule.details()}" + } + + location = with_text_range(location, line) + + %{ + ruleId: rule.id(), + severity: confidence_to_severity(confidence), + type: "VULNERABILITY", + engineId: "sobelow-#{vsn}", + primaryLocation: location + } + end + + defp with_text_range(%{} = location, line) when line > 0 do + Map.put(location, :textRange, %{startLine: line}) + end + + defp with_text_range(%{} = location, _line), do: location + + defp confidence_to_severity(:high), do: "CRITICAL" + defp confidence_to_severity(:medium), do: "MAJOR" + defp confidence_to_severity(:low), do: "MINOR" +end diff --git a/lib/utils/common_commands.ex b/lib/utils/common_commands.ex index f276e83..1486629 100644 --- a/lib/utils/common_commands.ex +++ b/lib/utils/common_commands.ex @@ -4,13 +4,11 @@ defmodule ElixirStructureManager.Utils.CommonCommands do def install_deps(cwd \\ nil), do: run_no_ci("mix", ["deps.get"], cwd) - def distillery_init(cwd \\ nil), do: run_no_ci("mix", ["distillery.init"], cwd) - - def config_distillery(cwd \\ nil), do: run("mix", ["ca.apply.config", "-t", "distillery"], cwd) + def config_sonar(cwd \\ nil), do: run("mix", ["ca.apply.config", "-t", "sonar"], cwd) def config_metrics(cwd \\ nil), do: run("mix", ["ca.apply.config", "-t", "metrics"], cwd) - defp run_no_ci(cmd, args, cwd) do + def run_no_ci(cmd, args, cwd) do ci_env = System.get_env("CI_ENV", "false") Logger.info("Resolved ci_env #{ci_env}") @@ -21,11 +19,17 @@ defmodule ElixirStructureManager.Utils.CommonCommands do end end - defp run(cmd, args, cwd) do - Mix.shell().info([:green, "Running '#{cmd} #{Enum.join(args, " ")}'"]) - System.cmd(cmd, args, into: IO.stream(), cd: resolve_dir(cwd)) + def run(cmd, args, cwd) do + log_cms = "#{cmd} #{Enum.join(args, " ")}" + Mix.shell().info([:green, "Running '#{log_cms}'"]) + {_, exit_code} = System.cmd(cmd, args, into: IO.stream(), cd: resolve_dir(cwd)) + + if exit_code != 0 do + Mix.shell().error([:red, "Command '#{log_cms}' failed with exit code #{exit_code}"]) + exit(exit_code) + end end - defp resolve_dir(nil), do: File.cwd!() - defp resolve_dir(dir), do: dir + def resolve_dir(nil), do: File.cwd!() + def resolve_dir(dir), do: dir end diff --git a/lib/utils/file_generator.ex b/lib/utils/file_generator.ex index 301d79d..9c2bf83 100644 --- a/lib/utils/file_generator.ex +++ b/lib/utils/file_generator.ex @@ -36,6 +36,11 @@ defmodule ElixirStructureManager.Utils.FileGenerator do apply(CommonCommands, task, []) end + defp transformation({:cmd, cmd}, tokens) do + [cmd | args] = String.split(resolve_content(cmd, tokens), " ") + CommonCommands.run(cmd, args, nil) + end + defp transformation({:inject_dependency = operation, dependency}, tokens) do transformation({operation, _dest_file = "mix.exs", dependency}, tokens) end diff --git a/lib/utils/token_helper.ex b/lib/utils/token_helper.ex index c869995..34ec2c2 100644 --- a/lib/utils/token_helper.ex +++ b/lib/utils/token_helper.ex @@ -2,36 +2,34 @@ defmodule ElixirStructureManager.Utils.TokenHelper do alias ElixirStructureManager.Utils.StringContent @moduledoc false - def add(token) when is_tuple(token) do + def add(token = {key, value}) when is_binary(key) and is_binary(value) do add(default_tokens(), token) end def add(key, value) when is_binary(key) and is_binary(value) do - StringContent.format_name(value) - |> to_token_list(key) - |> add(default_tokens()) + add(default_tokens(), key, value) end - def add(tokens, token) when is_list(token) and is_list(tokens) do + def add(tokens, token) when is_list(tokens) and is_list(token) do token ++ tokens end - def add(tokens, token) when is_tuple(token) and is_list(tokens) do - [token | tokens] + def add(tokens, {key, value}) when is_list(tokens) and is_binary(key) and is_binary(value) do + add(tokens, key, value) end - def add(tokens, key, value) when is_binary(key) and is_binary(value) and is_list(tokens) do - StringContent.format_name(value) - |> to_token_list(key) - |> add(tokens) + def add(tokens, key, value) when is_list(tokens) and is_binary(key) and is_binary(value) do + [{key, value} | tokens] end def add_boolean(tokens, key, value) when is_binary(key) do - add(tokens, {key, to_string(value || false)}) + add(tokens, key, to_string(value || false)) end def default_tokens do - case Mix.Project.config() |> Keyword.fetch(:app) do + config = Mix.Project.config() + + case Keyword.fetch(config, :app) do :error -> Mix.shell().error("It is not an elixir project") raise "It is not an elixir project" @@ -40,6 +38,7 @@ defmodule ElixirStructureManager.Utils.TokenHelper do to_string(app_name) |> StringContent.format_name() |> to_token_list() + |> add("{version}", config[:version]) end end @@ -52,8 +51,4 @@ defmodule ElixirStructureManager.Utils.TokenHelper do defp to_token_list({:ok, app_snake_name, app_camel_name}) do [{"{app}", app_camel_name}, {"{app_snake}", app_snake_name}] end - - defp to_token_list({:ok, app_snake_name, app_camel_name}, key) do - [{"{#{key}_snake}", app_snake_name}, {"{#{key}}", app_camel_name}] - end end diff --git a/mix.exs b/mix.exs index a692635..68956ca 100644 --- a/mix.exs +++ b/mix.exs @@ -54,10 +54,11 @@ defmodule ElixirStructureManager.MixProject do [ {:poison, "~> 5.0"}, {:castore, "~> 1.0"}, - {:mock, "~> 0.3.7", only: :test}, - {:excoveralls, "~> 0.18", only: :test}, - {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, - {:git_hooks, "~> 0.7", only: :dev, runtime: false}, + {:sobelow, "~> 0.13", only: [:dev, :test]}, + {:mock, "~> 0.3.7", only: [:dev, :test]}, + {:excoveralls, "~> 0.18", only: [:dev, :test]}, + {:ex_doc, ">= 0.0.0", only: [:dev, :test], runtime: false}, + {:git_hooks, "~> 0.7", only: [:dev, :test], runtime: false}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false} ] diff --git a/mix.lock b/mix.lock index c6e13fc..276a603 100644 --- a/mix.lock +++ b/mix.lock @@ -19,4 +19,5 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"}, "recase": {:hex, :recase, "0.7.0", "3f2f719f0886c7a3b7fe469058ec539cb7bbe0023604ae3bce920e186305e5ae", [:mix], [], "hexpm", "36f5756a9f552f4a94b54a695870e32f4e72d5fad9c25e61bc4a3151c08a4e0c"}, + "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, } diff --git a/test/apply_template_test.exs b/test/apply_template_test.exs index 094a17e..7cbbf9f 100644 --- a/test/apply_template_test.exs +++ b/test/apply_template_test.exs @@ -22,7 +22,6 @@ defmodule ApplyTemplatesTest do :cognitotokenprovider, :dynamo, :metrics, - :distillery, :sonar ], fn template -> diff --git a/test/ca_apply_config_test.exs b/test/ca_apply_config_test.exs index 4bc00b1..e292ee6 100644 --- a/test/ca_apply_config_test.exs +++ b/test/ca_apply_config_test.exs @@ -10,8 +10,8 @@ defmodule Ca.Apply.ConfigTest do test "should create a config" do with_mocks([{ApplyTemplate, [], [apply: fn _type, _name -> :ok end]}]) do - Task.run(["--type", "distillery"]) - assert called(ApplyTemplate.apply(:distillery, "non_required")) + Task.run(["--type", "metrics"]) + assert called(ApplyTemplate.apply(:metrics, "non_required")) end end end diff --git a/test/ca_new_structure_test.exs b/test/ca_new_structure_test.exs index 14c111c..08931e5 100644 --- a/test/ca_new_structure_test.exs +++ b/test/ca_new_structure_test.exs @@ -1,27 +1,27 @@ defmodule Ca.New.StructureTest do use ExUnit.Case - alias Mix.Tasks.Ca.New.Structure + alias Mix.Tasks.Ca.New.Structure, as: Task test "should shows helper information" do - assert :ok === Structure.run([]) + assert :ok === Task.run([]) end test "should returns the version" do - Structure.run(["-v"]) + Task.run(["-v"]) assert_received {:mix_shell, :info, ["Scaffold version v" <> _]} end test "should generate project" do project_name = "generated" - Structure.run([project_name]) + Task.run([project_name]) assert_received {:mix_shell, :info, ["iex -S mix"]} File.rm_rf(project_name) end - test "should generate project with metris and distillery" do + test "should generate project with metris and sonar" do project_name = "generated2" - Structure.run([project_name, "-m", "-d"]) + Task.run([project_name, "-m", "-s"]) assert_received {:mix_shell, :info, ["iex -S mix"]} assert File.exists?("generated2/lib/utils/custom_telemetry.ex") File.rm_rf(project_name) diff --git a/test/ca_release_test.exs b/test/ca_release_test.exs new file mode 100644 index 0000000..3c71479 --- /dev/null +++ b/test/ca_release_test.exs @@ -0,0 +1,18 @@ +defmodule Ca.ReleaseTest do + use ExUnit.Case + + import Mock + alias ElixirStructureManager.Utils.FileGenerator + alias Mix.Tasks.Ca.Release, as: Task + + test "should shows helper information" do + assert :ok === Task.run(["-h"]) + end + + test "should execute actions on project" do + with_mocks([{FileGenerator, [], [execute_actions: fn _actions, _tokens -> :ok end]}]) do + Task.execute({nil, []}) + assert called(FileGenerator.execute_actions(:_, :_)) + end + end +end diff --git a/test/ca_sobelow_sonar_test.exs b/test/ca_sobelow_sonar_test.exs new file mode 100644 index 0000000..ac63969 --- /dev/null +++ b/test/ca_sobelow_sonar_test.exs @@ -0,0 +1,28 @@ +defmodule Ca.Sobelow.SonarTest do + use ExUnit.Case + + import Mock + alias Mix.Tasks.Ca.Sobelow.Sonar, as: Task + + test "should shows helper information" do + assert :ok === Task.execute(["-h"]) + end + + test "should execute actions on project" do + json_sobelow = """ + {"findings":{"high_confidence":[{"line":0,"type":"Config.HTTPS: HTTPS Not Enabled","file":"config/prod.exs"}],"low_confidence":[{"line":9,"type":"RCE.CodeModule: Code Execution in `Code.eval_string`","file":"lib/some_use_case.ex","variable":"public_key"}],"medium_confidence":[{"line":98,"type":"XSS.SendResp: XSS in `send_resp`","file":"lib/rest_controller.ex","variable":"body"}]},"sobelow_version":"0.13.0","total_findings":3} + """ + + with_mocks([ + {File, [], + [ + exists?: fn _file -> true end, + read!: fn _file -> json_sobelow end, + write!: fn _file, _content -> :ok end + ]} + ]) do + Task.execute({[input: "sobelow.json", output: "sobelow_sonar.json"], []}) + assert called(File.write!("sobelow_sonar.json", :_)) + end + end +end diff --git a/test/ca_test_test.exs b/test/ca_test_test.exs new file mode 100644 index 0000000..641b328 --- /dev/null +++ b/test/ca_test_test.exs @@ -0,0 +1,18 @@ +defmodule Ca.TestTest do + use ExUnit.Case + + import Mock + alias ElixirStructureManager.Utils.FileGenerator + alias Mix.Tasks.Ca.Test, as: Task + + test "should shows helper information" do + assert :ok === Task.run(["-h"]) + end + + test "should execute actions on project" do + with_mocks([{FileGenerator, [], [execute_actions: fn _actions, _tokens -> :ok end]}]) do + Task.execute({nil, []}) + assert called(FileGenerator.execute_actions(:_, :_)) + end + end +end diff --git a/test/token_helper_test.exs b/test/token_helper_test.exs index 44665b5..d8533f0 100644 --- a/test/token_helper_test.exs +++ b/test/token_helper_test.exs @@ -16,6 +16,7 @@ defmodule TokenHelperTest do test "should add tuple with defaults" do expected = [ {"{app}", "Sample"}, + {"{version}", "0.1.8"}, {"{app}", "ElixirStructureManager"}, {"{app_snake}", "elixir_structure_manager"} ] @@ -27,15 +28,15 @@ defmodule TokenHelperTest do test "should add tokens to existing lis" do expected = [ + {"{key}", "value"}, + {"{version}", "0.1.8"}, {"{app}", "ElixirStructureManager"}, - {"{app_snake}", "elixir_structure_manager"}, - {"{key_snake}", "value"}, - {"{key}", "Value"} + {"{app_snake}", "elixir_structure_manager"} ] res = TokenHelper.default_tokens() - |> TokenHelper.add("key", "value") + |> TokenHelper.add("{key}", "value") assert expected === res end