gs-config/src/test/scala/gs/config/v0/audit/AuditedConfigurationTests.scala
Pat Garrity f127645860
All checks were successful
/ Build and Release Library (push) Successful in 1m29s
(patch) Initial implementation of the configuration library. (#1)
Reviewed-on: #1
2024-05-02 02:56:00 +00:00

345 lines
10 KiB
Scala

package gs.config.v0.audit
import cats.data.NonEmptyList
import cats.effect.IO
import gs.config.v0.AuditedConfiguration
import gs.config.v0.ConfigError
import gs.config.v0.ConfigKey
import gs.config.v0.ConfigName
import gs.config.v0.Configurable
import gs.config.v0.Configuration
import gs.config.v0.GsSuite
import gs.config.v0.source.ConfigSource
import gs.config.v0.source.MemoryConfigSource
import java.time.Instant
import java.time.LocalDate
class AuditedConfigurationTests extends GsSuite:
import AuditedConfigurationTests.*
given CanEqual[LocalDate, LocalDate] = CanEqual.derived
given CanEqual[Instant, Instant] = CanEqual.derived
iotest(
"should not return values, but should record attempts to find, when no config exists"
) {
for
config <- Configuration
.audited(ConfigSource.inMemory[IO](Map.empty))
.build()
string <- config.getValue(Keys.KString)
int <- config.getValue(Keys.KInt)
long <- config.getValue(Keys.KLong)
bool <- config.getValue(Keys.KBool)
localDate <- config.getValue(Keys.KLocalDate)
instant <- config.getValue(Keys.KInstant)
manifest <- config.manifest.snapshot()
yield
assertEquals(string, Left(ConfigError.MissingValue(Names.KString)))
assertEquals(int, Left(ConfigError.MissingValue(Names.KInt)))
assertEquals(long, Left(ConfigError.MissingValue(Names.KLong)))
assertEquals(bool, Left(ConfigError.MissingValue(Names.KBool)))
assertEquals(localDate, Left(ConfigError.MissingValue(Names.KLocalDate)))
assertEquals(instant, Left(ConfigError.MissingValue(Names.KInstant)))
assertMissing(config, Names.KString, manifest)
assertMissing(config, Names.KInt, manifest)
assertMissing(config, Names.KLong, manifest)
assertMissing(config, Names.KBool, manifest)
assertMissing(config, Names.KLocalDate, manifest)
assertMissing(config, Names.KInstant, manifest)
}
iotest(
"should not return values, but should record attempts to find, when no config exists, across multiple sources"
) {
for
config <- Configuration
.audited(ConfigSource.inMemory[IO](Map.empty))
.withMemorySource(Map.empty)
.withSource(ConfigSource.empty[IO])
.build()
string <- config.getValue(Keys.KString)
manifest <- config.manifest.snapshot()
yield
assertEquals(string, Left(ConfigError.MissingValue(Names.KString)))
assertEquals(
manifest.get(Names.KString),
Some(
List(
ConfigQueryResult.Failure(
sources = config.sources.toList.map(_.name),
error = ConfigError.MissingValue(Names.KString)
)
)
)
)
}
iotest("should find and audit a string value") {
testFound(Keys.KString, "test")
}
iotest("should find and audit a boolan value") {
testFound(Keys.KBool, true)
}
iotest("should find and audit an integer value") {
testFound(Keys.KInt, 11)
}
iotest("should find and audit a long value") {
testFound(Keys.KLong, 33L)
}
iotest("should find and audit a local date value") {
testFound(Keys.KLocalDate, LocalDate.now())
}
iotest("should find and audit an instant value") {
testFound(Keys.KInstant, Instant.now())
}
iotest("should find a value in the first source, skipping the next sources") {
val key = Keys.KString
val expectedValue = "value"
for
config <- Configuration
.audited(
ConfigSource
.inMemory[IO](Map(key.name.unwrap() -> expectedValue.toString()))
)
.withSource(ConfigSource.empty[IO])
.build()
value <- config.getValue(key)
manifest <- config.manifest.snapshot()
yield
assertEquals(value, Right(expectedValue))
assertSuccess(config, key.name, manifest, expectedValue.toString())
}
iotest("should instantiate for memory and environment sources") {
for
c1 <- AuditedConfiguration.forSource[IO](emptyMemorySource()).build()
c2 <- AuditedConfiguration.forEnvironmentSource[IO].build()
c3 <- AuditedConfiguration.forMemorySource[IO](Map.empty).build()
c4 <- AuditedConfiguration.forSources[IO](
NonEmptyList(emptyMemorySource(), List(emptyMemorySource()))
)
c5 <- AuditedConfiguration
.forEnvironmentSource[IO]
.withMemorySource(Map.empty)
.withEnvironmentSource()
.withSource(emptyMemorySource())
.build()
c6 <- Configuration.auditedEnvironmentOnly[IO]
yield
assertEquals(c1.sources.size, 1)
assertEquals(c2.sources.size, 1)
assertEquals(c3.sources.size, 1)
assertEquals(c4.sources.size, 2)
assertEquals(c5.sources.size, 4)
assertEquals(c6.sources.size, 1)
}
iotest("should audit the use of default values") {
val name = Names.KString
val defaultValue = "value"
val key = ConfigKey.WithDefaultValue(name, defaultValue)
for
config <- Configuration
.audited(ConfigSource.inMemory[IO](Map.empty))
.withMemorySource(Map.empty)
.build()
value <- config.getValue(key)
manifest <- config.manifest.snapshot()
yield
assertEquals(value, Right(defaultValue))
assertEquals(
manifest.get(name),
Some(
List(ConfigQueryResult.UsedDefault(config.sources.toList.map(_.name)))
)
)
}
iotest("should detect and audit parsing failures") {
val name = Names.KInt
val rawValue = "not-an-integer"
val key = ConfigKey.Required[Int](name)
for
config <- Configuration
.audited(ConfigSource.inMemory[IO](Map(name.unwrap() -> rawValue)))
.withMemorySource(Map.empty)
.build()
value <- config.getValue(key)
manifest <- config.manifest.snapshot()
yield
val expectedError = ConfigError.CannotParseValue(
configName = name,
candidateValue = rawValue,
source = config.sources.head.name
)
assertEquals(value, Left(expectedError))
assertEquals(
manifest.get(name),
Some(
List(
ConfigQueryResult.Failure(
sources = List(config.sources.head.name),
error = expectedError
)
)
)
)
}
iotest("should detect and audit parsing failures - EitherT") {
val name = Names.KInt
val rawValue = "not-an-integer"
val key = ConfigKey.Required[Int](name)
for
config <- Configuration
.audited(ConfigSource.inMemory[IO](Map(name.unwrap() -> rawValue)))
.withMemorySource(Map.empty)
.build()
value <- config.getValueT(key).value
manifest <- config.manifest.snapshot()
yield
val expectedError = ConfigError.CannotParseValue(
configName = name,
candidateValue = rawValue,
source = config.sources.head.name
)
assertEquals(value, Left(expectedError))
assertEquals(
manifest.get(name),
Some(
List(
ConfigQueryResult.Failure(
sources = List(config.sources.head.name),
error = expectedError
)
)
)
)
}
iotest("should audit multiple accesses of the same key") {
val key = Keys.KString
val expectedValue = "value"
for
config <- Configuration
.audited(
ConfigSource
.inMemory[IO](Map(key.name.unwrap() -> expectedValue.toString()))
)
.build()
v1 <- config.getValue(key)
v2 <- config.getValue(key)
v3 <- config.getValue(key)
manifest <- config.manifest.snapshot()
yield
assertEquals(v1, Right(expectedValue))
assertEquals(v2, Right(expectedValue))
assertEquals(v3, Right(expectedValue))
assertEquals(
manifest.get(key.name),
Some(
List(
ConfigQueryResult
.Success(Some(config.sources.head.name), expectedValue),
ConfigQueryResult
.Success(Some(config.sources.head.name), expectedValue),
ConfigQueryResult.Success(
Some(config.sources.head.name),
expectedValue
)
)
)
)
}
private def emptyMemorySource(): MemoryConfigSource[IO] =
MemoryConfigSource(Map.empty)
private def testFound[A: Configurable](
key: ConfigKey[A],
expectedValue: A
): IO[Any] =
for
config <- Configuration
.audited(
ConfigSource
.inMemory[IO](Map(key.name.unwrap() -> expectedValue.toString()))
)
.build()
value <- config.getValue(key)
manifest <- config.manifest.snapshot()
yield
assertEquals(value, Right(expectedValue))
assertSuccess(config, key.name, manifest, expectedValue.toString())
private def assertMissing(
config: AuditedConfiguration[IO],
name: ConfigName,
manifest: Map[ConfigName, List[ConfigQueryResult]]
): Unit =
assertEquals(
manifest.get(name),
Some(
List(
ConfigQueryResult.Failure(
sources = List(config.sources.head.name),
error = ConfigError.MissingValue(name)
)
)
)
)
private def assertSuccess(
config: AuditedConfiguration[IO],
name: ConfigName,
manifest: Map[ConfigName, List[ConfigQueryResult]],
expectedRawValue: String
): Unit =
assertEquals(
manifest.get(name),
Some(
List(
ConfigQueryResult.Success(
Some(config.sources.head.name),
expectedRawValue
)
)
)
)
object AuditedConfigurationTests:
object Names:
val KString: ConfigName = ConfigName("string")
val KInt: ConfigName = ConfigName("int")
val KLong: ConfigName = ConfigName("long")
val KBool: ConfigName = ConfigName("bool")
val KLocalDate: ConfigName = ConfigName("localdate")
val KInstant: ConfigName = ConfigName("instant")
end Names
object Keys:
val KString: ConfigKey[String] = ConfigKey.Required[String](Names.KString)
val KInt: ConfigKey[Int] = ConfigKey.Required[Int](Names.KInt)
val KLong: ConfigKey[Long] = ConfigKey.Required[Long](Names.KLong)
val KBool: ConfigKey[Boolean] = ConfigKey.Required[Boolean](Names.KBool)
val KLocalDate: ConfigKey[LocalDate] =
ConfigKey.Required[LocalDate](Names.KLocalDate)
val KInstant: ConfigKey[Instant] =
ConfigKey.Required[Instant](Names.KInstant)
end Keys
end AuditedConfigurationTests