|
| 1 | +#!/usr/bin/env elixir |
| 2 | +# Simple test generator that produces working tests. |
| 3 | +# Run: mix run scripts/gen_cli.exs |
| 4 | + |
| 5 | +defmodule GenCli do |
| 6 | + @moduledoc """ |
| 7 | + Generates test files for all CLI command modules. Uses a simpler |
| 8 | + approach than the previous generator — just direct string templating. |
| 9 | + """ |
| 10 | + @test_dir "test/ado_cli/cli" |
| 11 | + |
| 12 | + # Per-module specs: list of {function_name, http_method, path_template, args_template} |
| 13 | + @specs %{ |
| 14 | + "AdoCli.CLI.Areas" => [ |
| 15 | + {"list_areas", :get, "/test/_apis/wit/classificationnodes", "%{options: %{json: true}}"}, |
| 16 | + {"show_area", :get, "/test/_apis/wit/classificationnodes/1", "%{options: %{json: true}, arguments: %{id: 1, project: \"test\"}}"}, |
| 17 | + {"create_area", :post, "/test/_apis/wit/classificationnodes", "%{options: %{json: true, name: \"test\"}, arguments: %{project: \"test\"}}"}, |
| 18 | + {"update_area", :patch, "/test/_apis/wit/classificationnodes/1", "%{options: %{json: true, name: \"new\"}, arguments: %{id: 1, project: \"test\"}}"}, |
| 19 | + {"delete_area", :delete, "/test/_apis/wit/classificationnodes/1", "%{options: %{json: true, force: false}, arguments: %{id: 1, project: \"test\"}}"} |
| 20 | + ], |
| 21 | + "AdoCli.CLI.Banners" => [ |
| 22 | + {"show_banner", :get, "/_apis/notification/banners", "%{options: %{json: true}}"}, |
| 23 | + {"set_banner", :put, "/_apis/notification/banners", "%{options: %{json: true, message: \"test\"}}"}, |
| 24 | + {"delete_banner", :delete, "/_apis/notification/banners", "%{options: %{json: true, force: false}}"} |
| 25 | + ], |
| 26 | + "AdoCli.CLI.BranchPolicies" => [ |
| 27 | + {"list_policies", :get, "/test/_apis/policy/configurations", "%{options: %{json: true, ref_name: nil}, arguments: %{project: \"test\", repo_id: \"repo\"}}"}, |
| 28 | + {"show_policy", :get, "/test/_apis/policy/configurations/1", "%{options: %{json: true}, arguments: %{project: \"test\", repo_id: \"repo\", policy_id: 1}}"}, |
| 29 | + {"create_policy", :post, "/test/_apis/policy/configurations", "%{options: %{json: true, type: \"required reviewers\", settings: %{}, ref_name: \"main\"}, arguments: %{project: \"test\", repo_id: \"repo\"}}"}, |
| 30 | + {"update_policy", :put, "/test/_apis/policy/configurations/1", "%{options: %{json: true, is_enabled: true, is_blocking: false, settings: %{}}, arguments: %{project: \"test\", repo_id: \"repo\", policy_id: 1}}"}, |
| 31 | + {"delete_policy", :delete, "/test/_apis/policy/configurations/1", "%{options: %{json: true, force: false}, arguments: %{project: \"test\", repo_id: \"repo\", policy_id: 1}}"} |
| 32 | + ], |
| 33 | + "AdoCli.CLI.Builds" => [ |
| 34 | + {"list_builds", :get, "/test/_apis/build/builds", "%{options: %{json: true, top: nil, definitions: nil, branch: nil}, arguments: %{project: \"test\"}}"}, |
| 35 | + {"show_build", :get, "/test/_apis/build/builds/1", "%{options: %{json: true}, arguments: %{project: \"test\", build_id: 1}}"}, |
| 36 | + {"queue_build", :post, "/test/_apis/build/builds", "%{options: %{json: true, definition_id: 1, source_branch: nil, parameters: nil}, arguments: %{project: \"test\"}}"}, |
| 37 | + {"cancel_build", :patch, "/test/_apis/build/builds/1", "%{options: %{json: true}, arguments: %{project: \"test\", build_id: 1}}"}, |
| 38 | + {"list_tags", :get, "/test/_apis/build/builds/1/tags", "%{options: %{json: true}, arguments: %{project: \"test\", build_id: 1}}"}, |
| 39 | + {"add_tags", :put, "/test/_apis/build/builds/1/tags", "%{options: %{json: true, tags: \"tag1,tag2\"}, arguments: %{project: \"test\", build_id: 1}}"}, |
| 40 | + {"list_definitions", :get, "/test/_apis/build/definitions", "%{options: %{json: true, top: nil, name: nil, path: nil}, arguments: %{project: \"test\"}}"} |
| 41 | + ], |
| 42 | + "AdoCli.CLI.Connections" => [ |
| 43 | + {"list_connections", :get, "/test/_apis/serviceendpoint/endpoints", "%{options: %{json: true, top: nil, type: nil, include_details: false}, arguments: %{project: \"test\"}}"}, |
| 44 | + {"show_connection", :get, "/test/_apis/serviceendpoint/endpoints/1", "%{options: %{json: true, include_details: false}, arguments: %{project: \"test\", connection_id: 1}}"} |
| 45 | + ], |
| 46 | + "AdoCli.CLI.Extensions" => [ |
| 47 | + {"list_extensions", :get, "/_apis/extensionmanagement/installedextensions", "%{options: %{json: true, top: nil, include_disabled: false}}"}, |
| 48 | + {"show_extension", :get, "/_apis/extensionmanagement/installedextensions/1", "%{options: %{json: true}, arguments: %{ext: \"1\"}}"}, |
| 49 | + {"install_extension", :post, "/_apis/extensionmanagement/installedextensions", "%{options: %{json: true, publisher: \"ms\", extension: \"vss-services\"}}"}, |
| 50 | + {"uninstall_extension", :delete, "/_apis/extensionmanagement/installedextensions/1", "%{options: %{json: true, force: false, reason: \"test\"}, arguments: %{publisher: \"ms\", name: \"vss-services\"}}"}, |
| 51 | + {"enable_extension", :patch, "/_apis/extensionmanagement/installedextensions/1", "%{options: %{json: true}, arguments: %{publisher: \"ms\", name: \"vss-services\"}}"}, |
| 52 | + {"disable_extension", :patch, "/_apis/extensionmanagement/installedextensions/1", "%{options: %{json: true, reason: \"test\"}, arguments: %{publisher: \"ms\", name: \"vss-services\"}}"} |
| 53 | + ], |
| 54 | + "AdoCli.CLI.Folders" => [ |
| 55 | + {"list_folders", :get, "/test/_apis/pipelines/folders", "%{options: %{json: true, top: nil, query: nil}, arguments: %{project: \"test\"}}"}, |
| 56 | + {"create_folder", :post, "/test/_apis/pipelines/folders", "%{options: %{json: true, path: \"/test\"}, arguments: %{project: \"test\"}}"}, |
| 57 | + {"delete_folder", :delete, "/test/_apis/pipelines/folders/test", "%{options: %{json: true, force: false}, arguments: %{project: \"test\", path: \"test\"}}"} |
| 58 | + ], |
| 59 | + "AdoCli.CLI.Imports" => [ |
| 60 | + {"list_imports", :get, "/test/_apis/git/importRequests", "%{options: %{json: true, top: nil, include_abandoned: false}, arguments: %{project: \"test\"}}"}, |
| 61 | + {"show_import", :get, "/test/_apis/git/importRequests/1", "%{options: %{json: true}, arguments: %{project: \"test\", import_id: 1}}"}, |
| 62 | + {"create_import", :post, "/test/_apis/git/importRequests", "%{options: %{json: true, source: \"github\", endpoint: \"https://api.github.com\", repository: \"repo\"}, arguments: %{project: \"test\"}}"} |
| 63 | + ], |
| 64 | + "AdoCli.CLI.Iterations" => [ |
| 65 | + {"list_iterations", :get, "/test/_apis/work/teamsettings/iterations", "%{options: %{json: true, top: nil}, arguments: %{project: \"test\", team: \"Default Team\"}}"}, |
| 66 | + {"show_iteration", :get, "/test/_apis/work/teamsettings/iterations/1", "%{options: %{json: true}, arguments: %{project: \"test\", team: \"Default Team\", iteration_id: \"1\"}}"}, |
| 67 | + {"create_iteration", :post, "/test/_apis/work/teamsettings/iterations", "%{options: %{json: true, name: \"Sprint 1\", start_date: \"2024-01-01\", finish_date: \"2024-01-14\"}, arguments: %{project: \"test\", team: \"Default Team\"}}"}, |
| 68 | + {"update_iteration", :patch, "/test/_apis/work/teamsettings/iterations/1", "%{options: %{json: true, name: \"Sprint 1 Updated\"}, arguments: %{project: \"test\", team: \"Default Team\", iteration_id: \"1\"}}"}, |
| 69 | + {"delete_iteration", :delete, "/test/_apis/work/teamsettings/iterations/1", "%{options: %{json: true, force: false}, arguments: %{project: \"test\", team: \"Default Team\", iteration_id: \"1\"}}"} |
| 70 | + ], |
| 71 | + "AdoCli.CLI.Packages" => [ |
| 72 | + {"list_packages", :get, "/test/_apis/packaging/feeds", "%{options: %{json: true, top: nil}, arguments: %{project: \"test\"}}"}, |
| 73 | + {"list_versions", :get, "/test/_apis/packaging/feeds/1/packages", "%{options: %{json: true, top: nil}, arguments: %{project: \"test\", feed_id: 1}}"}, |
| 74 | + {"show_package", :get, "/test/_apis/packaging/feeds/1/packages/1", "%{options: %{json: true}, arguments: %{project: \"test\", feed_id: 1, package_id: \"1\"}}"} |
| 75 | + ], |
| 76 | + "AdoCli.CLI.PullRequests" => [ |
| 77 | + {"list_prs", :get, "/test/_apis/git/repositories/test/pullrequests", "%{options: %{json: true, top: nil, status: nil, creator: nil, reviewer: nil, source: nil, target: nil}, arguments: %{project: \"test\", repo_id: \"test\"}}"}, |
| 78 | + {"show_pr", :get, "/test/_apis/git/repositories/test/pullrequests/1", "%{options: %{json: true, include_commits: false, include_work_item_refs: false}, arguments: %{project: \"test\", repo_id: \"test\", pr_id: 1}}"}, |
| 79 | + {"create_pr", :post, "/test/_apis/git/repositories/test/pullrequests", "%{options: %{json: true, title: \"Test\", description: nil, source: \"refs/heads/feature\", target: \"refs/heads/main\", draft: false, work_items: nil, reviewers: nil, labels: nil}, arguments: %{project: \"test\", repo_id: \"test\"}}"}, |
| 80 | + {"complete_pr", :patch, "/test/_apis/git/repositories/test/pullrequests/1", "%{options: %{json: true, delete_source: false, merge_strategy: \"noFastForward\", merge_message: nil, squashed: false, bypass_policy: false, transition_work_items: false}, arguments: %{project: \"test\", repo_id: \"test\", pr_id: 1, completion_options: %{}}}"} |
| 81 | + ], |
| 82 | + "AdoCli.CLI.Releases" => [ |
| 83 | + {"list_releases", :get, "/test/_apis/release/releases", "%{options: %{json: true, top: nil, definition_id: nil}, arguments: %{project: \"test\"}}"}, |
| 84 | + {"show_release", :get, "/test/_apis/release/releases/1", "%{options: %{json: true, include_artifacts: false, expand: \"none\"}, arguments: %{project: \"test\", release_id: 1}}"} |
| 85 | + ], |
| 86 | + "AdoCli.CLI.Repos" => [ |
| 87 | + {"list_repos", :get, "/test/_apis/git/repositories", "%{options: %{json: true, top: nil, include_links: false, include_all_urls: false}, arguments: %{project: \"test\"}}"}, |
| 88 | + {"show_repo", :get, "/test/_apis/git/repositories/test", "%{options: %{json: true, include_links: false}, arguments: %{project: \"test\", repo_id: \"test\"}}"}, |
| 89 | + {"list_branches", :get, "/test/_apis/git/repositories/test/refs", "%{options: %{json: true, top: nil, filter: nil}, arguments: %{project: \"test\", repo_id: \"test\"}}"}, |
| 90 | + {"create_repo", :post, "/test/_apis/git/repositories", "%{options: %{json: true, name: \"new-repo\", default_branch: nil, parent_repo: nil}, arguments: %{project: \"test\"}}"}, |
| 91 | + {"delete_repo", :delete, "/test/_apis/git/repositories/test", "%{options: %{json: true, force: false}, arguments: %{project: \"test\", repo_id: \"test\"}}"} |
| 92 | + ], |
| 93 | + "AdoCli.CLI.Security" => [ |
| 94 | + {"list_groups", :get, "/_apis/graph/groups", "%{options: %{json: true, top: nil, scope_descriptor: nil, subject_types: nil}}"}, |
| 95 | + {"show_group", :get, "/_apis/graph/groups/1", "%{options: %{json: true, expand: false}, arguments: %{group_id: \"1\"}}"}, |
| 96 | + {"create_group", :post, "/_apis/graph/groups", "%{options: %{json: true, display_name: \"New Group\", description: nil, scope_descriptor: \"scp\"}}"}, |
| 97 | + {"delete_group", :delete, "/_apis/graph/groups/1", "%{options: %{json: true, force: false}, arguments: %{group_id: \"1\"}}"}, |
| 98 | + {"list_members", :get, "/_apis/graph/groups/1/memberships", "%{options: %{json: true, top: nil}, arguments: %{group_id: \"1\"}}"}, |
| 99 | + {"list_namespaces", :get, "/_apis/securitynamespaces", "%{options: %{json: true, top: nil, local_only: false}}"}, |
| 100 | + {"list_permissions", :get, "/_apis/securitynamespaces/2/permissions", "%{options: %{json: true, top: nil}, arguments: %{namespace_id: \"2\"}}"} |
| 101 | + ], |
| 102 | + "AdoCli.CLI.Teams" => [ |
| 103 | + {"list_teams", :get, "/test/_apis/teams", "%{options: %{json: true, top: nil, mine: false, expand_identity: false, subject_types: nil}, arguments: %{project: \"test\"}}"}, |
| 104 | + {"show_team", :get, "/test/_apis/teams/1", "%{options: %{json: true, expand_identity: false}, arguments: %{project: \"test\", team_id: \"1\"}}"}, |
| 105 | + {"create_team", :post, "/test/_apis/teams", "%{options: %{json: true, name: \"New Team\", description: nil}, arguments: %{project: \"test\"}}"}, |
| 106 | + {"update_team", :patch, "/test/_apis/teams/1", "%{options: %{json: true, name: \"Updated Team\", description: nil}, arguments: %{project: \"test\", team_id: \"1\"}}"}, |
| 107 | + {"delete_team", :delete, "/test/_apis/teams/1", "%{options: %{json: true, force: false}, arguments: %{project: \"test\", team_id: \"1\"}}"}, |
| 108 | + {"list_team_members", :get, "/test/_apis/teams/1/members", "%{options: %{json: true, top: nil}, arguments: %{project: \"test\", team_id: \"1\"}}"} |
| 109 | + ], |
| 110 | + "AdoCli.CLI.Users" => [ |
| 111 | + {"list_users", :get, "/_apis/identities", "%{options: %{json: true, top: nil, filter: nil, subject_types: nil}}"}, |
| 112 | + {"show_user", :get, "/_apis/identities/1", "%{options: %{json: true}, arguments: %{user_id: \"1\"}}"}, |
| 113 | + {"add_user", :post, "/_apis/identities", "%{options: %{json: true, descriptor: \"vssgp.Uy0xLTkt\"}}"}, |
| 114 | + {"remove_user", :delete, "/_apis/identities/1", "%{options: %{json: true, force: false}, arguments: %{user_id: \"1\"}}"} |
| 115 | + ], |
| 116 | + "AdoCli.CLI.Wikis" => [ |
| 117 | + {"list_wikis", :get, "/test/_apis/wiki/wikis", "%{options: %{json: true, top: nil}, arguments: %{project: \"test\"}}"}, |
| 118 | + {"show_wiki", :get, "/test/_apis/wiki/wikis/1", "%{options: %{json: true}, arguments: %{project: \"test\", wiki_id: \"1\"}}"}, |
| 119 | + {"list_pages", :get, "/test/_apis/wiki/wikis/1/pages", "%{options: %{json: true, top: nil, path: nil, recursion_level: nil}, arguments: %{project: \"test\", wiki_id: \"1\"}}"}, |
| 120 | + {"show_page", :get, "/test/_apis/wiki/wikis/1/pages/test", "%{options: %{json: true, include_content: true, recursion_level: \"full\"}, arguments: %{project: \"test\", wiki_id: \"1\", path: \"test\"}}"}, |
| 121 | + {"create_page", :put, "/test/_apis/wiki/wikis/1/pages", "%{options: %{json: true, path: \"/test\", content: \"test\"}, arguments: %{project: \"test\", wiki_id: \"1\"}}"}, |
| 122 | + {"update_page", :patch, "/test/_apis/wiki/wikis/1/pages", "%{options: %{json: true, path: \"/test\", content: \"updated\"}, arguments: %{project: \"test\", wiki_id: \"1\"}}"} |
| 123 | + ], |
| 124 | + "AdoCli.CLI.Logout" => [ |
| 125 | + {"run", :noop, "", "%{}"} |
| 126 | + ], |
| 127 | + "AdoCli.CLI.Whoami" => [ |
| 128 | + {"run", :noop, "", "%{}"} |
| 129 | + ] |
| 130 | + } |
| 131 | + |
| 132 | + def run do |
| 133 | + File.mkdir_p!(@test_dir) |
| 134 | + |
| 135 | + for {module, specs} <- @specs do |
| 136 | + if File.exists?(test_path(module)) do |
| 137 | + IO.puts(" Skipping #{Path.basename(test_path(module))} (exists)") |
| 138 | + else |
| 139 | + generate(module, specs) |
| 140 | + end |
| 141 | + end |
| 142 | + end |
| 143 | + |
| 144 | + defp test_path(module) do |
| 145 | + base = module |> String.split(".") |> List.last() |> Macro.underscore() |
| 146 | + "#{@test_dir}/#{base}_test.exs" |
| 147 | + end |
| 148 | + |
| 149 | + defp generate(module, specs) do |
| 150 | + content = render(module, specs) |
| 151 | + File.write!(test_path(module), content) |
| 152 | + IO.puts(" Generated #{Path.basename(test_path(module))} (#{length(specs)} subcommands)") |
| 153 | + end |
| 154 | + |
| 155 | + defp render(module, specs) do |
| 156 | + tests_block = Enum.map_join(specs, "\n\n", fn spec -> render_spec(module, spec) end) |
| 157 | + |
| 158 | + """ |
| 159 | + defmodule #{module}Test do |
| 160 | + use AdoCli.CLI.TestHelper |
| 161 | + alias #{module} |
| 162 | +
|
| 163 | + #{tests_block} |
| 164 | + end |
| 165 | + """ |
| 166 | + end |
| 167 | + |
| 168 | + defp render_spec(_module, {fn_name, method, path, args}) when method == :noop do |
| 169 | + """ |
| 170 | + describe "#{fn_name}" do |
| 171 | + test "halts 0 on success" do |
| 172 | + apply(#{noop_module(fn_name)}, :#{fn_name}, [#{args}]) |
| 173 | + assert_receive {:cli_mate_shell, :halt, 0}, 500 |
| 174 | + end |
| 175 | + end |
| 176 | + """ |
| 177 | + end |
| 178 | + |
| 179 | + defp noop_module("run"), do: "AdoCli.CLI.Logout" |
| 180 | + defp noop_module(_), do: "AdoCli.CLI.Whoami" |
| 181 | + |
| 182 | + defp render_spec(module, {fn_name, method, path, args}) do |
| 183 | + method_str = |
| 184 | + case method do |
| 185 | + :get -> "expect_success_json" |
| 186 | + :post -> "expect_post_success" |
| 187 | + :put -> "expect_put_success" |
| 188 | + :patch -> "expect_patch_success" |
| 189 | + :delete -> "expect_delete_success" |
| 190 | + end |
| 191 | + |
| 192 | + body_arg = |
| 193 | + case method do |
| 194 | + :get -> ~s(, ~s({\\"value\\":[]})) |
| 195 | + m when m in [:post, :put, :patch] -> ~s(, "", "{\\"id\\":1}") |
| 196 | + :delete -> "" |
| 197 | + end |
| 198 | + |
| 199 | + """ |
| 200 | + describe "#{fn_name}" do |
| 201 | + test "halts 0 on successful #{method}", %{server: server} do |
| 202 | + #{method_str}(server, "#{path}"#{body_arg}, fn -> |
| 203 | + apply(#{module}, :#{fn_name}, [#{args}]) |
| 204 | + end) |
| 205 | + end |
| 206 | +
|
| 207 | + test "halts 1 on API error", %{server: server} do |
| 208 | + expect_api_error(server, "#{path}", 500, "{}", fn -> |
| 209 | + apply(#{module}, :#{fn_name}, [#{args}]) |
| 210 | + end) |
| 211 | + end |
| 212 | + end |
| 213 | + """ |
| 214 | + end |
| 215 | +end |
| 216 | + |
| 217 | +GenCli.run() |
0 commit comments