Skip to content

Commit 088cd3a

Browse files
committed
Add comprehensive config tests
- Test all ConfigError ADT types and messages - Test Environment conversions and current detection - Test all type-safe accessors and optional variants - Test throw accessors for error cases - Test process-scoped config behavior and immutability - 23 tests covering all config functionality
1 parent 6bf96d6 commit 088cd3a

File tree

1 file changed

+148
-50
lines changed

1 file changed

+148
-50
lines changed
Lines changed: 148 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,188 @@
11
package test.cask
22

3+
import cask.Config
4+
import cask.Config.ConfigError
5+
import cask.Config.Environment
36
import utest._
47

58
object ConfigTests extends TestSuite {
69

710
val tests = Tests {
8-
test("ADT error types") {
9-
val missing = cask.Config.ConfigError.Missing("test.key")
10-
assert(missing.message.contains("missing"))
11+
test("ConfigError ADT") {
12+
test("Missing") {
13+
val missing = ConfigError.Missing("test.key")
14+
assert(missing.message.contains("missing"))
15+
assert(missing.message.contains("test.key"))
16+
}
17+
18+
test("InvalidType") {
19+
val invalidType = ConfigError.InvalidType("test.key", "String", "Int")
20+
assert(invalidType.message.contains("expected"))
21+
assert(invalidType.message.contains("String"))
22+
assert(invalidType.message.contains("Int"))
23+
}
24+
25+
test("LoadFailure") {
26+
val loadFailure = ConfigError.LoadFailure("file not found")
27+
assert(loadFailure.message.contains("Failed to load"))
28+
assert(loadFailure.message.contains("file not found"))
29+
}
1130

12-
val invalidType = cask.Config.ConfigError.InvalidType("test.key", "String", "Int")
13-
assert(invalidType.message.contains("expected"))
31+
test("ParseFailure") {
32+
val parseFailure = ConfigError.ParseFailure("test.key", "invalid", "syntax error")
33+
assert(parseFailure.message.contains("test.key"))
34+
assert(parseFailure.message.contains("invalid"))
35+
assert(parseFailure.message.contains("syntax error"))
36+
}
1437
}
1538

16-
test("Environment from string") {
17-
import cask.Config.Environment._
39+
test("Environment") {
40+
test("fromString conversions") {
41+
assert(Environment.fromString("dev") == Environment.Development)
42+
assert(Environment.fromString("development") == Environment.Development)
43+
assert(Environment.fromString("test") == Environment.Test)
44+
assert(Environment.fromString("prod") == Environment.Production)
45+
assert(Environment.fromString("production") == Environment.Production)
46+
47+
val custom = Environment.fromString("staging")
48+
assert(custom.isInstanceOf[Environment.Custom])
49+
assert(custom.name == "staging")
50+
}
51+
52+
test("current environment") {
53+
val env = Environment.current
54+
assert(env.isInstanceOf[Environment])
55+
assert(env.name.nonEmpty)
56+
}
1857

19-
assert(fromString("dev") == Development)
20-
assert(fromString("development") == Development)
21-
assert(fromString("test") == Test)
22-
assert(fromString("prod") == Production)
23-
assert(fromString("production") == Production)
24-
assert(fromString("staging").isInstanceOf[Custom])
58+
test("environment names") {
59+
assert(Environment.Development.name == "dev")
60+
assert(Environment.Test.name == "test")
61+
assert(Environment.Production.name == "prod")
62+
assert(Environment.Custom("staging").name == "staging")
63+
}
2564
}
2665

27-
test("Config loader Either pattern") {
28-
val result = cask.Config.getString("nonexistent.key")
29-
assert(result.isLeft)
66+
test("Config getString") {
67+
test("missing key returns Left") {
68+
val result = Config.getString("nonexistent.key")
69+
assert(result.isLeft)
3070

31-
result match {
32-
case Left(error) => assert(error.message.contains("missing"))
33-
case Right(_) => assert(false)
71+
result match {
72+
case Left(error) =>
73+
assert(error.isInstanceOf[ConfigError.Missing])
74+
assert(error.message.contains("missing"))
75+
case Right(_) => assert(false)
76+
}
77+
}
78+
79+
test("pattern matching on Either") {
80+
Config.getString("nonexistent") match {
81+
case Left(_: ConfigError.Missing) => // Expected
82+
case _ => assert(false)
83+
}
3484
}
3585
}
3686

37-
test("Optional config accessors") {
38-
val opt = cask.Config.getStringOpt("nonexistent.key")
39-
assert(opt.isEmpty)
87+
test("Config optional accessors") {
88+
test("getStringOpt returns None for missing") {
89+
val opt = Config.getStringOpt("nonexistent.key")
90+
assert(opt.isEmpty)
91+
}
92+
93+
test("getIntOpt returns None for missing") {
94+
val opt = Config.getIntOpt("nonexistent.key")
95+
assert(opt.isEmpty)
96+
}
97+
98+
test("getBooleanOpt returns None for missing") {
99+
val opt = Config.getBooleanOpt("nonexistent.key")
100+
assert(opt.isEmpty)
101+
}
40102
}
41103

42-
test("Type-safe accessors with different types") {
43-
// These will work if application.conf exists with proper values
44-
// Test that methods exist and return correct types
45-
locally {
46-
val _: Either[cask.Config.ConfigError, String] = cask.Config.getString("any.key")
104+
test("Config type-safe accessors") {
105+
test("getString returns Either") {
106+
val _: Either[ConfigError, String] = Config.getString("any.key")
47107
}
48-
locally {
49-
val _: Either[cask.Config.ConfigError, Int] = cask.Config.getInt("any.key")
108+
109+
test("getInt returns Either") {
110+
val _: Either[ConfigError, Int] = Config.getInt("any.key")
50111
}
51-
locally {
52-
val _: Either[cask.Config.ConfigError, Boolean] = cask.Config.getBoolean("any.key")
112+
113+
test("getBoolean returns Either") {
114+
val _: Either[ConfigError, Boolean] = Config.getBoolean("any.key")
53115
}
54-
locally {
55-
val _: Either[cask.Config.ConfigError, Long] = cask.Config.getLong("any.key")
116+
117+
test("getLong returns Either") {
118+
val _: Either[ConfigError, Long] = Config.getLong("any.key")
56119
}
57-
locally {
58-
val _: Either[cask.Config.ConfigError, Double] = cask.Config.getDouble("any.key")
120+
121+
test("getDouble returns Either") {
122+
val _: Either[ConfigError, Double] = Config.getDouble("any.key")
59123
}
60124
}
61125

62-
test("Environment detection from CASK_ENV") {
63-
val env = cask.Config.Environment.current
64-
// Should default to Development if CASK_ENV not set
65-
assert(env.isInstanceOf[cask.Config.Environment])
126+
test("Config throw accessors") {
127+
test("getStringOrThrow throws on missing") {
128+
try {
129+
Config.getStringOrThrow("nonexistent.key")
130+
assert(false)
131+
} catch {
132+
case e: RuntimeException => assert(e.getMessage.contains("missing"))
133+
}
134+
}
135+
136+
test("getIntOrThrow throws on missing") {
137+
try {
138+
Config.getIntOrThrow("nonexistent.key")
139+
assert(false)
140+
} catch {
141+
case e: RuntimeException => assert(e.getMessage.contains("missing"))
142+
}
143+
}
144+
145+
test("getBooleanOrThrow throws on missing") {
146+
try {
147+
Config.getBooleanOrThrow("nonexistent.key")
148+
assert(false)
149+
} catch {
150+
case e: RuntimeException => assert(e.getMessage.contains("missing"))
151+
}
152+
}
66153
}
67154

68-
test("Config error pattern matching") {
69-
import cask.Config.ConfigError._
155+
test("Config underlying Typesafe Config access") {
156+
val underlying = Config.underlying
157+
assert(underlying != null)
158+
assert(underlying.isInstanceOf[com.typesafe.config.Config])
159+
}
70160

71-
val errors: Seq[cask.Config.ConfigError] = Seq(
72-
Missing("key1"),
73-
InvalidType("key2", "String", "Int"),
74-
LoadFailure("cause"),
75-
ParseFailure("key3", "value", "cause")
161+
test("ConfigError pattern matching") {
162+
val errors: Seq[ConfigError] = Seq(
163+
ConfigError.Missing("key1"),
164+
ConfigError.InvalidType("key2", "String", "Int"),
165+
ConfigError.LoadFailure("cause"),
166+
ConfigError.ParseFailure("key3", "value", "cause")
76167
)
77168

78169
errors.foreach { error =>
79170
error match {
80-
case Missing(key) => assert(key.nonEmpty)
81-
case InvalidType(key, expected, actual) =>
171+
case ConfigError.Missing(key) => assert(key.nonEmpty)
172+
case ConfigError.InvalidType(key, expected, actual) =>
82173
assert(key.nonEmpty && expected.nonEmpty && actual.nonEmpty)
83-
case LoadFailure(cause) => assert(cause.nonEmpty)
84-
case ParseFailure(key, value, cause) =>
174+
case ConfigError.LoadFailure(cause) => assert(cause.nonEmpty)
175+
case ConfigError.ParseFailure(key, value, cause) =>
85176
assert(key.nonEmpty && cause.nonEmpty)
86177
}
87178
}
88179
}
180+
181+
test("Config is process-scoped and immutable") {
182+
// Multiple accesses return same underlying config
183+
val config1 = Config.underlying
184+
val config2 = Config.underlying
185+
assert(config1 eq config2)
186+
}
89187
}
90188
}

0 commit comments

Comments
 (0)