yak(credo-strict-tuned-config-both-apps-x02d): credo strict, tuned config (both apps)
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
defmodule Blogex.LinkErrorTest do
|
||||
@moduledoc """
|
||||
Pins the rendered exception message, not just the struct.
|
||||
|
||||
The message is what a compile-time failure shows a human: the indentation and
|
||||
the newline between errors are load-bearing. This is a characterization test
|
||||
written before `message/1` was rewritten (`Enum.map/2 |> Enum.join/2` ->
|
||||
`Enum.map_join/3`), so it passes on both sides of that change by design - it
|
||||
exists to make the rewrite safe, not to prove new behaviour.
|
||||
"""
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test "message/1 renders one indented line per error" do
|
||||
error = %Blogex.LinkError{
|
||||
blog: :engineering,
|
||||
post_id: "my-post",
|
||||
errors: [
|
||||
{1, "/blog/unknown/broken", "unknown blog ID: unknown", [post_id: "my-post"]},
|
||||
{7, "/blog/engineering/", "empty slug", [post_id: "my-post"]}
|
||||
]
|
||||
}
|
||||
|
||||
assert Exception.message(error) == """
|
||||
invalid internal blog links in engineering (post: my-post)
|
||||
|
||||
line 1: /blog/unknown/broken — unknown blog ID: unknown
|
||||
line 7: /blog/engineering/ — empty slug
|
||||
"""
|
||||
end
|
||||
|
||||
test "message/1 omits the post label when there is no post id" do
|
||||
error = %Blogex.LinkError{
|
||||
blog: :engineering,
|
||||
post_id: nil,
|
||||
errors: [{3, "/blog/unknown/broken", "unknown blog ID: unknown", []}]
|
||||
}
|
||||
|
||||
assert Exception.message(error) == """
|
||||
invalid internal blog links in engineering
|
||||
|
||||
line 3: /blog/unknown/broken — unknown blog ID: unknown
|
||||
"""
|
||||
end
|
||||
end
|
||||
@@ -355,5 +355,12 @@ defmodule Blogex.LinkValidatorTest do
|
||||
assert LinkValidator.parse_blog_link("/blog/engineering/hello-world#top") ==
|
||||
{"engineering", "hello-world"}
|
||||
end
|
||||
|
||||
# Only the paths that cannot be a blog link return nil; a nested slug
|
||||
# ("/blog/eng/a/post") still parses and is rejected later by validate_slug/1.
|
||||
test "returns nil for anything that is not /blog/<blog>/<slug>" do
|
||||
refute LinkValidator.parse_blog_link("/blog/engineering")
|
||||
refute LinkValidator.parse_blog_link("/notblog/engineering/a-post")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
defmodule Blogex.CredoConfigTest do
|
||||
@moduledoc """
|
||||
Pins the *policy* in `.credo.exs`, not just the file's presence.
|
||||
|
||||
The gate is `mix credo --strict` (see the `credo` target in `Makefile`), so
|
||||
the config is load-bearing: a check silently re-enabled here changes what
|
||||
every future commit has to satisfy. These assertions are deliberately coarse
|
||||
- "this check sits in that list", "the disable says why" - so they survive
|
||||
credo's own boilerplate churn but not a policy change.
|
||||
"""
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@config_path ".credo.exs"
|
||||
@complexity_check "Credo.Check.Refactor.CyclomaticComplexity"
|
||||
|
||||
test "cyclomatic complexity is disabled in favour of CRAP" do
|
||||
checks = default_config()[:checks]
|
||||
|
||||
assert @complexity_check in check_modules(checks[:disabled])
|
||||
refute @complexity_check in check_modules(checks[:enabled])
|
||||
end
|
||||
|
||||
test "the disable carries a rationale comment" do
|
||||
rationale = rationale_above(@complexity_check)
|
||||
|
||||
refute rationale == [], "no comment block above the #{@complexity_check} entry"
|
||||
assert Enum.join(rationale, "\n") =~ ~r/crap/i
|
||||
end
|
||||
|
||||
defp default_config do
|
||||
{config, _bindings} = Code.eval_file(@config_path)
|
||||
Enum.find(config.configs, &(&1.name == "default"))
|
||||
end
|
||||
|
||||
defp check_modules(checks) do
|
||||
Enum.map(checks, fn
|
||||
{module, _opts} -> module_name(module)
|
||||
module -> module_name(module)
|
||||
end)
|
||||
end
|
||||
|
||||
# `Atom.to_string/1` keeps the "Elixir." prefix that `inspect/1` drops; the
|
||||
# assertions read better (and diff better) without it.
|
||||
defp module_name(module) do
|
||||
module |> Atom.to_string() |> String.replace_prefix("Elixir.", "")
|
||||
end
|
||||
|
||||
# The contiguous `#` block immediately above the check's own line. The entry
|
||||
# line is the one that is not itself a comment: a comment mentioning the
|
||||
# check by name must not be mistaken for the entry.
|
||||
defp rationale_above(check_name) do
|
||||
lines = @config_path |> File.read!() |> String.split("\n")
|
||||
|
||||
index =
|
||||
Enum.find_index(lines, fn line ->
|
||||
String.contains?(line, check_name) and not comment?(line)
|
||||
end)
|
||||
|
||||
lines
|
||||
|> Enum.take(index)
|
||||
|> Enum.reverse()
|
||||
|> Enum.take_while(&comment?/1)
|
||||
end
|
||||
|
||||
defp comment?(line), do: line |> String.trim() |> String.starts_with?("#")
|
||||
end
|
||||
@@ -10,17 +10,19 @@ defmodule Blogex.Test.AlphaBlog do
|
||||
tests can assert exact orderings rather than re-deriving them.
|
||||
"""
|
||||
|
||||
alias Blogex.Test.PostBuilder
|
||||
|
||||
def blog_id, do: :alpha
|
||||
|
||||
def all_posts,
|
||||
do: [Blogex.Test.PostBuilder.build(id: "a1", date: ~D[2026-03-01], blog: :alpha)]
|
||||
do: [PostBuilder.build(id: "a1", date: ~D[2026-03-01], blog: :alpha)]
|
||||
|
||||
def all_tags, do: ["elixir"]
|
||||
|
||||
def unfiltered_posts,
|
||||
do: [
|
||||
Blogex.Test.PostBuilder.build(id: "a1", date: ~D[2026-03-01], blog: :alpha),
|
||||
Blogex.Test.PostBuilder.build(
|
||||
PostBuilder.build(id: "a1", date: ~D[2026-03-01], blog: :alpha),
|
||||
PostBuilder.build(
|
||||
id: "a-draft",
|
||||
date: ~D[2026-03-05],
|
||||
blog: :alpha,
|
||||
@@ -34,16 +36,18 @@ defmodule Blogex.Test.BetaBlog do
|
||||
The second blog module for cross-blog tests; see `Blogex.Test.AlphaBlog`.
|
||||
"""
|
||||
|
||||
alias Blogex.Test.PostBuilder
|
||||
|
||||
def blog_id, do: :beta
|
||||
|
||||
def all_posts,
|
||||
do: [Blogex.Test.PostBuilder.build(id: "b1", date: ~D[2026-03-15], blog: :beta)]
|
||||
do: [PostBuilder.build(id: "b1", date: ~D[2026-03-15], blog: :beta)]
|
||||
|
||||
def all_tags, do: ["devops"]
|
||||
|
||||
def unfiltered_posts,
|
||||
do: [
|
||||
Blogex.Test.PostBuilder.build(id: "b1", date: ~D[2026-03-15], blog: :beta),
|
||||
Blogex.Test.PostBuilder.build(id: "b-future", date: ~D[2099-01-01], blog: :beta)
|
||||
PostBuilder.build(id: "b1", date: ~D[2026-03-15], blog: :beta),
|
||||
PostBuilder.build(id: "b-future", date: ~D[2099-01-01], blog: :beta)
|
||||
]
|
||||
end
|
||||
|
||||
@@ -5,6 +5,8 @@ defmodule Blogex.Test.Setup do
|
||||
|
||||
import Blogex.Test.PostBuilder
|
||||
|
||||
alias Blogex.Test.FakeBlog
|
||||
|
||||
@doc """
|
||||
Starts a FakeBlog with a standard set of posts.
|
||||
Returns `%{blog: module, posts: posts}`.
|
||||
@@ -13,9 +15,9 @@ defmodule Blogex.Test.Setup do
|
||||
posts = Keyword.get(opts, :posts, default_posts())
|
||||
blog_opts = Keyword.drop(opts, [:posts])
|
||||
|
||||
{:ok, _} = Blogex.Test.FakeBlog.start(posts, blog_opts)
|
||||
{:ok, _} = FakeBlog.start(posts, blog_opts)
|
||||
|
||||
Map.merge(context, %{blog: Blogex.Test.FakeBlog, posts: posts})
|
||||
Map.merge(context, %{blog: FakeBlog, posts: posts})
|
||||
end
|
||||
|
||||
@doc "A small set of posts covering common scenarios."
|
||||
|
||||
Reference in New Issue
Block a user