Skip to content

Commit 2a9893c

Browse files
committed
added exhaustiveness tests for config code examples
1 parent e3de588 commit 2a9893c

5 files changed

Lines changed: 190 additions & 88 deletions

File tree

site/app/docs/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export default function DocsPage() {
104104
rel="noopener noreferrer"
105105
className="rounded-md border border-neutral-200 px-4 py-2 font-medium text-neutral-900 transition-colors hover:bg-neutral-100 dark:border-neutral-800 dark:text-neutral-100 dark:hover:bg-neutral-800"
106106
>
107-
View on GitHub
107+
GitHub
108108
</a>
109109
</div>
110110

site/app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export default function Home() {
8282
target="_blank"
8383
rel="noopener noreferrer"
8484
>
85-
View on GitHub
85+
GitHub
8686
</Link>
8787
</Button>
8888
<Button variant="secondary" size="lg" asChild>

test/code_examples/configuration_test.rb

Lines changed: 5 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -6,92 +6,6 @@
66
module LogStruct
77
module CodeExamples
88
class ConfigurationTest < ActiveSupport::TestCase
9-
class MockUser < T::Struct
10-
const :id, Integer
11-
const :email, String
12-
end
13-
14-
class MockRequest < T::Struct
15-
const :remote_ip, String
16-
end
17-
18-
def test_basic_logging
19-
user = MockUser.new(id: 123, email: "user@example.com")
20-
# ----------------------------------------------------------
21-
# BEGIN CODE EXAMPLE: basic_logging
22-
# ----------------------------------------------------------
23-
# Log a simple string
24-
Rails.logger.info "User signed in"
25-
26-
# Log a hash with custom fields
27-
Rails.logger.info({
28-
event: "user_login",
29-
user_id: user.id,
30-
ip_address: "192.168.1.1",
31-
custom_field: "any value you want"
32-
})
33-
# ----------------------------------------------------------
34-
# END CODE EXAMPLE: basic_logging
35-
# ----------------------------------------------------------
36-
assert_equal 123, user.id # Stop minitest complaining
37-
end
38-
39-
def test_getting_started_basic_logging
40-
user = MockUser.new(id: 123, email: "user@example.com")
41-
request = MockRequest.new(remote_ip: "192.168.1.1")
42-
43-
# Set up tagged logger
44-
Rails.logger = ActiveSupport::TaggedLogging.new(Rails.logger)
45-
46-
# ----------------------------------------------------------
47-
# BEGIN CODE EXAMPLE: getting_started_basic_logging
48-
# ----------------------------------------------------------
49-
# Log a simple message
50-
Rails.logger.info "User signed in"
51-
52-
# Log structured data
53-
Rails.logger.info({
54-
src: "rails",
55-
evt: "user_login",
56-
user_id: user.id,
57-
ip_address: request.remote_ip
58-
})
59-
60-
# Log with tags
61-
Rails.logger.tagged("Authentication") do
62-
Rails.logger.info "User signed in"
63-
Rails.logger.info(user_id: user.id, ip_address: request.remote_ip)
64-
end
65-
# ----------------------------------------------------------
66-
# END CODE EXAMPLE: getting_started_basic_logging
67-
# ----------------------------------------------------------
68-
assert_equal 123, user.id # Stop minitest complaining
69-
end
70-
71-
def test_typed_logging
72-
# ----------------------------------------------------------
73-
# BEGIN CODE EXAMPLE: typed_logging
74-
# ----------------------------------------------------------
75-
# Create a typed log entry
76-
log = LogStruct::Log::Plain.new(
77-
source: LogStruct::Source::App,
78-
message: "User signed in",
79-
additional_data: {
80-
user_id: 123,
81-
ip_address: "192.168.1.1"
82-
}
83-
)
84-
85-
# Log the typed struct
86-
LogStruct.info(log)
87-
# ----------------------------------------------------------
88-
# END CODE EXAMPLE: typed_logging
89-
# ----------------------------------------------------------
90-
91-
# Simple assertion to make the test pass
92-
assert_instance_of LogStruct::Log::Plain, log
93-
end
94-
959
def test_basic_configuration
9610
# ----------------------------------------------------------
9711
# BEGIN CODE EXAMPLE: basic_configuration
@@ -140,12 +54,17 @@ def test_integrations_configuration
14054
config.integrations.enable_activestorage = true
14155
config.integrations.enable_ahoy = true
14256
config.integrations.enable_carrierwave = true
57+
config.integrations.enable_color_output = true
58+
config.integrations.enable_dotenv = true
59+
config.integrations.enable_goodjob = true
14360
config.integrations.enable_host_authorization = true
14461
config.integrations.enable_lograge = true
14562
config.integrations.enable_rack_error_handler = true
63+
config.integrations.enable_semantic_logger = true
14664
config.integrations.enable_shrine = true
14765
config.integrations.enable_sidekiq = true
14866
config.integrations.enable_sorbet_error_handlers = true
67+
config.integrations.enable_sql_logging = true
14968

15069
# Configure custom options for Lograge
15170
config.integrations.lograge_custom_options = ->(event, _) {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# typed: true
2+
# frozen_string_literal: true
3+
4+
require "test_helper"
5+
6+
module LogStruct
7+
module CodeExamples
8+
class ExhaustivenessTest < ActiveSupport::TestCase
9+
ROOT = File.expand_path("../..", __dir__)
10+
EXAMPLE_FILE = File.join(ROOT, "test", "code_examples", "configuration_test.rb")
11+
12+
def test_integrations_configuration_is_exhaustive
13+
block = extract_example_block("integrations_configuration")
14+
props = props_from_struct(File.join(ROOT, "lib", "log_struct", "config_struct", "integrations.rb"))
15+
.select { |p| p.start_with?("enable_") }
16+
17+
assert_props_present!(
18+
block: block,
19+
props: props,
20+
prefixes: ["config.integrations."],
21+
hint: "config.integrations.NAME_HERE = true"
22+
)
23+
end
24+
25+
def test_filter_configuration_is_exhaustive
26+
block = extract_example_block("filter_configuration")
27+
props = props_from_struct(File.join(ROOT, "lib", "log_struct", "config_struct", "filters.rb"))
28+
29+
assert_props_present!(
30+
block: block,
31+
props: props,
32+
prefixes: ["config.filters."],
33+
hint: "config.filters.NAME_HERE = ..."
34+
)
35+
end
36+
37+
def test_error_handling_modes_configuration_is_exhaustive
38+
block = extract_example_block("error_handling_modes")
39+
props = props_from_struct(File.join(ROOT, "lib", "log_struct", "config_struct", "error_handling_modes.rb"))
40+
41+
# Accept either `modes.<name>` or `config.error_handling_modes.<name>`
42+
assert_props_present!(
43+
block: block,
44+
props: props,
45+
prefixes: ["modes.", "config.error_handling_modes."],
46+
hint: "modes.NAME_HERE = LogStruct::ErrorHandlingMode::..."
47+
)
48+
end
49+
50+
private
51+
52+
def extract_example_block(id)
53+
content = File.read(EXAMPLE_FILE)
54+
begin_marker = /\bBEGIN CODE EXAMPLE:\s*#{Regexp.escape(id)}\b/
55+
end_marker = /\bEND CODE EXAMPLE:\s*#{Regexp.escape(id)}\b/
56+
57+
start_idx = content.index(begin_marker)
58+
59+
assert start_idx, "BEGIN marker for #{id} not found in #{EXAMPLE_FILE}"
60+
after_start = content[start_idx..]
61+
62+
end_idx = after_start.index(end_marker)
63+
64+
assert end_idx, "END marker for #{id} not found in #{EXAMPLE_FILE}"
65+
66+
after_start[0...end_idx]
67+
end
68+
69+
def props_from_struct(path)
70+
File.read(path)
71+
.each_line
72+
.filter_map { |line| (m = line.match(/\bprop\s+:([a-z0-9_]+)\b/)) && m[1] }
73+
.uniq
74+
end
75+
76+
def assert_props_present!(block:, props:, prefixes:, hint:)
77+
missing = props.reject do |name|
78+
prefixes.any? { |pref| block.include?("#{pref}#{name}") }
79+
end
80+
81+
assert_empty missing, <<~MSG
82+
Missing settings in code example:\n\n - #{missing.join("\n - ")}\n\n Add lines like:\n #{hint}\n\n Example location: #{EXAMPLE_FILE}
83+
MSG
84+
end
85+
end
86+
end
87+
end

test/code_examples/logging_test.rb

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# typed: true
2+
# frozen_string_literal: true
3+
4+
require "test_helper"
5+
6+
module LogStruct
7+
module CodeExamples
8+
class LoggingTest < ActiveSupport::TestCase
9+
class MockUser < T::Struct
10+
const :id, Integer
11+
const :email, String
12+
end
13+
14+
class MockRequest < T::Struct
15+
const :remote_ip, String
16+
end
17+
18+
def test_basic_logging
19+
user = MockUser.new(id: 123, email: "user@example.com")
20+
# ----------------------------------------------------------
21+
# BEGIN CODE EXAMPLE: basic_logging
22+
# ----------------------------------------------------------
23+
# Log a simple string
24+
Rails.logger.info "User signed in"
25+
26+
# Log a hash with custom fields
27+
Rails.logger.info({
28+
event: "user_login",
29+
user_id: user.id,
30+
ip_address: "192.168.1.1",
31+
custom_field: "any value you want"
32+
})
33+
# ----------------------------------------------------------
34+
# END CODE EXAMPLE: basic_logging
35+
# ----------------------------------------------------------
36+
assert_equal 123, user.id # Stop minitest complaining
37+
end
38+
39+
def test_getting_started_basic_logging
40+
user = MockUser.new(id: 123, email: "user@example.com")
41+
request = MockRequest.new(remote_ip: "192.168.1.1")
42+
43+
# Set up tagged logger
44+
Rails.logger = ActiveSupport::TaggedLogging.new(Rails.logger)
45+
46+
# ----------------------------------------------------------
47+
# BEGIN CODE EXAMPLE: getting_started_basic_logging
48+
# ----------------------------------------------------------
49+
# Log a simple message
50+
Rails.logger.info "User signed in"
51+
52+
# Log structured data
53+
Rails.logger.info({
54+
src: "rails",
55+
evt: "user_login",
56+
user_id: user.id,
57+
ip_address: request.remote_ip
58+
})
59+
60+
# Log with tags
61+
Rails.logger.tagged("Authentication") do
62+
Rails.logger.info "User signed in"
63+
Rails.logger.info(user_id: user.id, ip_address: request.remote_ip)
64+
end
65+
# ----------------------------------------------------------
66+
# END CODE EXAMPLE: getting_started_basic_logging
67+
# ----------------------------------------------------------
68+
assert_equal 123, user.id # Stop minitest complaining
69+
end
70+
71+
def test_typed_logging
72+
# ----------------------------------------------------------
73+
# BEGIN CODE EXAMPLE: typed_logging
74+
# ----------------------------------------------------------
75+
# Create a typed log entry
76+
log = LogStruct::Log::Plain.new(
77+
source: LogStruct::Source::App,
78+
message: "User signed in",
79+
additional_data: {
80+
user_id: 123,
81+
ip_address: "192.168.1.1"
82+
}
83+
)
84+
85+
# Log the typed struct
86+
LogStruct.info(log)
87+
# ----------------------------------------------------------
88+
# END CODE EXAMPLE: typed_logging
89+
# ----------------------------------------------------------
90+
91+
# Simple assertion to make the test pass
92+
assert_instance_of LogStruct::Log::Plain, log
93+
end
94+
end
95+
end
96+
end

0 commit comments

Comments
 (0)