I am writing unit test to verify the expectation that whether custom region is passed correctly.
def methodToTest(bucket: String, key: Path, customRegion) {
S3.download(bucket, key, None, None, None)
.withAttributes(S3Attributes.settings.getCustomSettings(s3Settings, customRegion))
.....
}
The #getCustomSettings is a private method which takes region as parameter and returns S3Settings as follows:
private def getCustomSettings(s3Settings: S3Settings, customRegion: String): S3Settings = {
settings.withS3RegionProvider(new AWSRegionProvider {
override def getRegion: Region = Region.of(customRegion)
})
}
I tried separately testing the private method but I want to test the verification of expectation to check what custom region is passed to it using scalatest? Thanks
EDIT: S3Settings is a final class which cannot be mocked like mock[S3Settings]
If getCustomSettings is private, you cannot be calling it like that from outside of the S3Attributes.settings instance, so I am going to assume you are mistaken about that.
Now, to answer your question, you need to mock the settings instance, used by the method getting tested. Something like this for example (assuming the type of S3Attributes.settings is S:
def methodToTest(
bucket: String,
key: Path,
customRegion: String,
settings: S = S3Attributes.settings
) = {
S3.download(bucket, key, None, None, None)
.withAttributes(settings.getCustomSettings(s3Settings, customRegion))
.....
}
Then in your test:
val settings = mock[S]
when(settings.getCustomSettings(any, any)).thenAnswer {
(s: S3Settings, _: String) => s
}
methodToTest("foo", "bar", "baz", settings)
verify(settings).getCustomSettings(any, eq("baz"))
Related
I'm implementing a client for the Keystone API of Openstack. For the Users I have the following classes:
import java.time.OffsetDateTime
import io.circe.derivation.{deriveDecoder, deriveEncoder, renaming}
import io.circe.{Decoder, Encoder}
object User {
object Update {
implicit val encoder: Encoder[Update] = deriveEncoder(renaming.snakeCase)
}
case class Update(
name: Option[String] = None,
password: Option[String] = None,
defaultProjectId: Option[String] = None,
enabled: Option[Boolean] = None,
)
implicit val decoder: Decoder[User] = deriveDecoder(renaming.snakeCase)
}
final case class User(
id: String,
name: String,
domainId: String,
defaultProjectId: Option[String] = None,
passwordExpiresAt: Option[OffsetDateTime] = None,
enabled: Boolean = true,
)
Where User.Update contains the possible fields to update a user. Updates are done using PATCH, or in other words they are partial. The encoders are being used in a class which has the methods to create, update, delete, get, and list the users. This service class uses the encoders in a http4s EntityEncoder with:
import io.circe.{Encoder, Printer}
import org.http4s.{EntityDecoder, circe}
val jsonPrinter: Printer = Printer.noSpaces.copy(dropNullValues = true)
implicit def jsonEncoder[A: Encoder]: EntityEncoder[F, A] = circe.jsonEncoderWithPrinterOf(jsonPrinter)
My problem is how to implement the update for defaultProjectId. In the final json sent to the server the following cases are possible:
Keep the current value of defaultProjectId (the json object does not contain the field default_project_id:
{
(...)
}
Change the defaultProjectId to an-id:
{
(...),
"default_project_id: "an-id",
(...)
}
Unset the defaultProjectId:
{
(...),
"default_project_id: null,
(...)
}
The current implementation: defaultProjectId: Option[String] = None + dropNullValues in the printer, models correctly the cases 1 and 2, but prevents case 3.
Ideally I would have an ADT like:
sealed trait Updatable[+T]
case object KeepExistingValue extends Updatable[Nothing]
case object Unset extends Updatable[Nothing]
case class ChangeTo[T](value: T) extends Updatable[T]
Usage example (probably in the future all fields would be Updatables):
case class Update(
name: Option[String] = None,
password: Option[String] = None,
defaultProjectId: Updatable[String] = KeepExistingValue,
enabled: Option[Boolean] = None,
)
But I can't find a clean way to encode this ADT. Attempted solutions and their problems (they all require not using the printer with dropNullValues in the update method):
Unset is special:
// The generic implementation of Updatable
implicit def updatableEncoder[T](implicit valueEncoder: Encoder[T]): Encoder[Updatable[T]] = {
case KeepExistingValue => Json.Null
case Unset => Json.fromString(Unset.getClass.getName) // Or another arbitrary value
case ChangeTo(value) => valueEncoder(value)
}
// In the service class
def nullifyUnsets(obj: JsonObject): JsonObject = obj.mapValues {
case json if json.asString.contains(Unset.getClass.getName) => Json.Null
case json => json
}
def update(id: String, update: Update): F[Model] = {
// updateEncoder is of type Encoder[Update]
updateEncoder(update).dropNullValues.mapObject(nullifyUnsets)
(...)
}
Pros:
Using the dropNullValues nicely handles the KeepExistingValue case.
If the user invokes dropNullValues to derive the encoder the code still works.
Cons:
Because of dropNullValues the Unset case is meh.
We iterate twice on the Json Object field/values, once for dropNullValues another for mapObject.
Json.fromString(Unset.getClass.getName) is arbitrary and can collide with a legit value for T, although very unlikely.
KeepExistingValue is special:
// The generic implementation of Updatable
implicit def updatableEncoder[T](implicit valueEncoder: Encoder[T]): Encoder[Updatable[T]] = {
case KeepExistingValue => Json.fromString(Unset.getClass.getName) // Or another arbitrary value
case Unset => Json.Null
case ChangeTo(value) => valueEncoder(value)
}
// In the service class
def dropKeepExistingValues(obj: JsonObject): JsonObject = obj.filter{
case (_, json) => !json.asString.contains(Unset.getClass.getName)
}
def update(id: String, update: Update): F[Model] = {
// updateEncoder is of type Encoder[Update]
updateEncoder(update).mapObject(dropKeepExistingValues)
(...)
}
Pros:
Simpler implementation, updatableEncoder implementation maps more directly to the needed Json.
Just one pass over the Json Object field/values.
Cons:
If the programmer invokes dropNullValues to derive the encoder then the code stops working.
Json.fromString(Unset.getClass.getName) is arbitrary and can collide with a legit value for T, although very unlikely.
I'm sure I'm not the first one to hit this problem, but I can't search for it, the best I got is this comment.
I'm building an API, that takes in a variable path parameter, or dynamic part of the route, as the play documentation would specify it.
I would like to validate this as to give the client a proper response.
I have the following route setup
GET /:dynamic/all controller.method(dynamic: String)
The dynamic param for the method is used across the API, for multiple methods, so i would like to get some kind of global validation/whitelist of acceptable strings. (eg: "hello"/"hi" would be accepted, and "noooway" would not be accepted, and i would return a 404 not found as response.
I would preferably like my controller method to not contain any validation so that this would be true:
def method(dynamic: String): Action[AnyContent] = Action.async { _ =>
//I already know "dynamic" is valid here.
Future.successful(Ok(Json.toJson(Map("status" -> "OK"))))
}
Instead of: (excuse my javaisc-psuedo-code)
def method(dynamic: String): Action[AnyContent] = Action.async { _ =>
val valid = Helper.validate(dynamic)
if (!valid) return some result/response else
Future.successful(Ok(Json.toJson(Map("status" -> "OK"))))
}
Play allows you to do this by different ways.
1. PathBindable
You can implement a PathBindable[T] for any type T, so that your value extracted from the path of the request is not a simple String but a T.
If you are ready to change the type of dynamic (which would make sense, since it is not supposed to be just any string but a valid one), you could do the following:
case class Validated(str: String) {
assert(Helper.validate(str))
}
object Validated {
implicit val pathBindable = new PathBindable[Validated] {
val string = implicitly[PathBindable[String]]
override def bind(key: String, value: String): Either[String, Validated] =
string.bind(key, value). // bind as if it were a string
right.filter(Helper.validate).getOrElse(Left("Invalid input")). // filter using your validation function, and give error message
right.map(Validated(_)) // encapsulate in your new type
override def unbind(key: String, value: Validated): String =
string.unbind(key, value.str) //unbind as if it were a string
}
}
Note that you need to implement unbind for reverse routing (get a path for a given action call).
Now, you just need to replace String in your router and in your controller by your.package.Validated.
GET /:dynamic/all controller.method(dynamic: your.package.Validated)
NB: if you want to use the simple name of your class, you need to import it in your build.sbt:
(project in file(".").
enablePlugins(PlayScala).
settings(routesImport += "your.package.Validated")
2. Action Composition
You can also implement an action filter to be used whenever your input needs to be validated:
case class ValidatedAction(input: String) extends ActionFilter[Request] {
override protected def filter[A](request: Request[A]): Future[Option[Result]] = Future.successful{
if (Helper.validate(input)) None else Some(BadRequest("Invalid input"))
}
}
def method(dynamic: String) = (Action andThen ValidatedAction(dynamic)).async {
Future.successful(Ok)
}
The code inside the async block will be executed only if the filter method returns None, otherwise, it will return the specified Result (here, BadRequest("Invalid input").
To give you a minimal example:
object Main extends JSApp
{
val someThing: String = determineSomething("test")
def main(): Unit =
{
println(someThing)
}
}
Now, two possibilities here:
private def determineSomething(s: String): String = "succeeded"
If the project is executed like this, well, I get a log entry saying
succeeded
But when I use the more functional syntax:
private val determineSomething: (s: String) => "succeeded"
I get
TypeError: this.determineSomething$1 is null
I am curious as to the why this happens as in the (JVM) repl, both ways work perfectly fine.
I think what you want is something like this:
object Main extends JSApp {
private val determineSomething: String => String = (s: String) => "succeeded"
val someThing: String = determineSomething("test")
def main(): Unit = {
println(someThing)
}
}
The declaration of determineSomething needs to come before the declaration of something, otherwise the former will be uninitialized when the compiler attempts to initialize the latter.
I can't seem to figure out how to chain together these functions, any help or advice would be appreciated.
// Generic approach to adding flags to a command string
trait UpdateCommandString {
def update[T](option: Option[T], flagName: String)(implicit command: String): String = {
if (option.isEmpty)
command
else if (option.get.isInstanceOf[Boolean]) {
if (option.get.asInstanceOf[Boolean])
s"$command $flagName"
command
} else
s"$command $flagName ${option.get.asInstanceOf[String]}"
}
}
// One example of flags (the program I'm using has literally 50+ flags
// so there will be a number of case classes that group them into related
// sets)
case class Flags(cache: Option[String] = None,
errorlog: Option[String] = None,
accesslog: Option[String] = None,
verbose: Option[Boolean] = Some(false),
auth: Option[Boolean] = Some(false)) extends UpdateCommandString {
def applyToCommand(implicit command: String): String = {
// These seem to apply separately, but I want to chain
// them together!
update(cache, "-cache")
update(errorlog, "-error")
update(accesslog, "-access")
update(auth, "-do-auth")
}
}
// An example of what I'm trying to do
// Given a base command string and a bunch of case classes to apply
// to that string, I'd like to be able to call applyToCommand and
// get back the modified command string
var command = "run_system"
val f = Flags(Some("asdfasdf"), None, None, Some(true), Some(false))
command = f.applyToCommand(command)
I would recommend a complete redesign of your current approach.
Every member of your Flags class should be it's own case class, extending a common Flag class.
So you can define functions to combine different flags to one configuration. This configuration can than, in a final step, be used to build your result string.
abstract class Flag(name: String, parameter : Option[String])
case class Cache(parameter : Option[String]) extends Flag("-cache", parameter)
case class ErrorLog(parameter : Option[String]) extends Flag("-errorlog", parameter)
//...
type Config = List[Flag]
def applyToCommand(commandName : String, config : Config) = {
def buildString(f:Flag) =
s" $f.name${f.parameter.map(" " ++ _).getOrElse("")}"
val flagsString = config.map(buildString).mkString("")
s"$commandName" ++ flagString
}
//Now you can it simply use it as I described above
val config = List(Cache(Some("asdf")), ErrorLog(None))
applyToCommand("run_system", config)
This makes your code more flexible and easier to refactor.
At last here are some advises how you could modify this design to better fit your needs:
If you need to group your flags, you can put them in objects or separate files. Or if you want to change their behavior based on the group you can enhance the class hierarchy and add an intermediate layer.
You can move the parameter from Flag down to the case classes, so every Flag can define if it needs parameters, if yes how many and if those are optional or not.
You could also implement buildString at the case classes so every flag can decide how to format it self.
If you want do add new Flags you simply add a new class and that's it, no need to add anything to an unrelated class.
As explained #bmaderbacher, I think you should separate the different flags in the different case class.
But to answer your question, you should modify applyToCommand:
def applyToCommand(implicit command: String): String = {
var s = update(cache, "-cache")(command)
s = update(errorlog, "-error")(s)
s = update(accesslog, "-access")(s)
s = update(auth, "-do-auth")(s)
s
}
At this point it should be clear that you didn't make the right choice for your Flag class.
I'll do something like that:
trait Flag {
def toString: String
}
case class Command(value: String) {
def add(flag: Flag) = Command(value + ' ' + flag.toString)
def +(flag: Flag) = add(flag)
}
case class Cache(size: Int) extends Flag {
def toString = s"--cache $size"
}
case object Auth extends Flag {
def toString = "--auth"
}
Now you can do something like:
val command = Command("run") + Cache(500) + Auth
Let's say I have an action that optionally accepts two parameters:
def foo(name: String, age: Integer) = Action {
// name & age can both be null if not passed
}
How do I setup my route file to work with any of the following call syntaxes:
/foo
/foo?name=john
/foo?age=18
/foo?name=john&age=18
/foo?authCode=bar&name=john&age=18 // The controller may have other implicit parameters
What is the correct syntax for this?
Something like this should work:
GET /foo controllers.MyController.foo(name: String ?= "", age: Int ?= 0)
Since your parameters can be left off you need to provide default values for them (and handle those values in the controller function).
You should be able to access other optional parameters in the controller if you pass in an implicit request and access the getQueryString parameter (added in Play 2.1.0 I think):
def foo(name: String, age: Integer) = Action { implicit request =>
val authCode: Option[String] = request.getQueryString("authCode")
...
}
A nicer way to do it might just be to take your optional name and age out of the controller parameters and extract everything from the queryString:
def foo = Action { implicit request =>
val nameOpt: Option[String] = request.getQueryString("name")
val ageOpt: Option[String] = request.getQueryString("age")
...
}
Update: The current docs for 2.1.1 are a bit off about this (since fixed with issue #776) but this is another (and the best, IMHO) option:
GET /foo controllers.MyController.foo(name: Option[String], age: Option[Int])
And...
def foo(name: Option[String], age: Option[Int]) = Action { implicit request =>
Ok(s"Name is: $name, age is $age")
}