How to write a test for an http get request in elm - elm

I'm trying to check the data received after sending a request and for that I need to write a test to make sure of it.
These are the three functions handling the data.
fetchConfigs : Cmd Msg
fetchConfigs =
Http.get
{ url = "http://localhost:8080/api/sso/configs"
, expect = Http.expectJson GotConfigs responseDecoder
}
responseDecoder : Decoder Config
responseDecoder =
map2 Config
(field "identifier" string)
(field "ssoType" int)
type Msg
= GotConfigs (Result Http.Error Config)

Have a look at https://package.elm-lang.org/packages/avh4/elm-program-test/latest/ProgramTest. From the package description:
This module allows you to interact with your program by simulating
user interactions and external events (like HTTP responses and ports),
and making assertions about the HTML it renders and the external
requests it makes.

Related

Reactive Spring - Request body is missing -

I have a Reactive Rest Controller that makes a POST call along with Flux<> Request Body.
I want to do sequential steps in this Post call
Validate Request Body input
Delete all rows based on one field inside Request Body.
Save all data from this Flux Request Body
Return all updated saved data from Flux to Controller
Sample Code
public Mono<Boolean> update(Flux<RequestBody> body, Long id) {.
return body.
.flatMap(d -> this.repo.deleteCriteria(id, d.getValue);
.thenMany(body)
.flatMap(m-> {mapper to convert from DTO to Entity});
.flatMap(s -> this.repo.save(s.name, s.data)
.then(c -> this.repo.findById(id).count)
.then(n-> n.intValue() > 0 ? Mono.just(true) : Mono.just(false));
}.
Controller code -
#ResponseStatus(HttpStatus.CREATED).
#PostMapping("/abc").
public Mono<ResponseEntity<Void> update(Long id, Flux<Body> body) {
Mono<Boolean> onSuccess = this.service.update(id, body);
onSuccess
.log()
.flatMap(b -> b ? Mono.just(new ResponseEntity<>(HttpStatus.CREATED) :
Mono.just(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}
I see in Logs that Delete statement getting executed n times. Ideally I wanted it only one time to run but I only know flatMap().
But after delete statement I see below error in logs.
"Error - 400 Bad Request - Request body is missing ....."
FYI - Before when I only had a delete statement and I was not fetching value from Flux Request Body everything was working fine.
Please let me know if there is a way to
read values from Request Body and do validations.
perform multiple operations on this Request body like Delete, save and find by reading some values from the RequestBody.
Any help appreciated :)

How to avoid Expect msg with HTTP 2.0.0 in elm?

In elm's Http 1.0.0 package, I could send a custom request like:
post : Endoint -> List (Http.Header) -> Http.Body -> Decoder a -> Http.Request a
post url headers body decoder =
Http.request
{ method = "POST"
, headers = headers
, url = url
, body = body
, expect = Http.expectJson decoder
, timeout = Nothing
, withCredentials = False
}
With the post function I wrote above, I can simply call it with, say, a Decoder String, and after the Http request sends, the response string will be decoded and returned. period. No need to create a Msg like:
type Msg
= GotText (Result Http.Error String)
And no need to write a branch in update to handle this Msg.
However, as of Http 2.0.0, the expect argument is of type Expect msg, not Expect a, meaning that writing the Msg variation and additional branch to update will now be required.
I am writing an Api.elm file which makes Http requests. However, this means that now it will have to have its own Msg type and update function to run after these requests respond.
I used to think that Home.elm should only respond to messages from Home.Msg and Home.update not Api.Msg and Api.update. Am I wrong? Should Api.elm have its own Msg type and update function that changes other pages? Or would there be a better way to do this?
To clarify what I was explaining in my question:
A custom request in Elm's HTTP 2.0.0 package looks like this:
request :
{ method : String
, headers : List Header
, url = String
, body = Body
, expect = Expect msg
, timeout = Maybe Float
, withCredentials = Maybe String
}
-> Cmd msg
Wheras in Http 1.0.0 it looked like this:
request :
{ method : String
, headers : List Header
, url : String
, body : Body
, expect : Expect a
, timeout : Maybe Float
, withCredentials : Bool
}
-> Request a
The difference is that using the custom request from HTTP 2.0.0, I needed to pass-in a Msg to use this request.
Now, my problem was: I was using an Api.elm file. Every time I needed to issue an HTTP request, I would call Api.request arg1 arg2... from, say, Login.elm.
Since the request function in Api.elm demanded a Msg type, for, in this case, a login request I thought I would have to define a Msg of GotLogin within Api.elm and then handle how GotLogin would update Login.elm by writing an update branch for GotLogin within Api.elm.
However, I could just define a GotLogin Msg in Login.elm and pass that into Api.request. Since I've defined GotLogin in Login.elm, I would put a GotLogin branch in the update function of Login.elm, instead of Api.elm.
This also applies for any other request type from any other page (Signup.elm, Home.elm,...), meaning that Api.elm, should not have its own update function that updates other pages.
The whole point of Login.elm having its own update function, is that it should only be affected by the branches of its own update function, not ones from Api.elm.

Elm type confusion

I started on my first, simple web app in Elm. Most of my code is currently adapted from https://github.com/rtfeldman/elm-spa-example. I am working against a API that will give me a authToken in the response header. I have a AuthToken type that is supposed to represent that token. Taking the value out of the header and converting it to a result that's either a error String or a AuthToken is causing trouble. I expected that I could just say I am returning a AuthToken, return a String and it would be fine because my AuthTokens right now are just Strings. It seems like there clearly is something about Elm types I am not understanding.
Here is the definition of AuthToken:
type AuthToken
= AuthToken String
and my way too complicated function that for now just tries to do some type changes (later I want to also do work on the body in here):
authTokenFromHeader : String -> Http.Response String -> Result String AuthToken
authTokenFromHeader name resp =
let
header = extractHeader name resp
in
case header of
Ok header ->
let
token : Result String AuthToken
token = Ok (AuthToken header)
in
token
Err error -> Err error
I expected the happy case would return a Ok result with the string from the response header converted to a AuthToken as its value. Instead I am getting Cannot find variable 'AuthToken'. From the documentation I expected to get a constructor with the same name as the type. If I just use Ok header, the compiler is unhappy because I am returning Result String String instead of the promised Result String AuthToken.
What's the right approach here?
The code looks fine as is. The error message indicates that type AuthToken has been defined in a different module and not imported completely to the module that defines authTokenFromHeader. You can read about Elm's module system in the Elm guide: Modules.
A possible fix, assuming that type AuthToken is defined in module Types, and authTokenFromHeader is defined in module Net, is:
Types.elm:
module Types exposing (AuthToken(..))
type AuthToken = AuthToken String
Net.elm:
module Net exposing (authTokenFromHeader)
import Types exposing (AuthToken(..))
authTokenFromHeader : String -> Http.Response String -> Result String AuthToken
authTokenFromHeader name resp =
...
Note the use of AuthToken(..) instead of just AuthToken, which ensures that the type as well as the type constructors are imported/exported.
Or just move the definition of type AuthToken into the same file as the definition of authTokenFromHeader.

Intercepting 401 for any response in Elm

I'm building an application in Elm where most API calls are protected; i.e. the user needs to be logged in for the API call to work. If the user is not logged in, they will receive a 401 Unauthorized response. I want the application to redirect to the login page if any response is a 401.
Currently, I only have this redirect set up for a single API call. Here is a stripped-down version of the code to give an idea of how it's set up:
-- Util/Api.elm
type alias Data data =
{ data : data
}
-- Resources/Expense.elm
getExpenses : (Progress (Api.Data (List Expense)) -> msg) -> Sub msg
getExpenses msg =
(dataDecoder expenseListDecoder)
|> Http.get expensesEndpoint
|> Progress.track expensesEndpoint msg
-- Main/Msg.elm
type Msg
= ExpenseListMsg ExpenseListMsg
| RedirectToLogin
-- Main/Update.elm
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ExpenseListMsg msg ->
ExpenseList.Update.update msg model
GoTo path ->
model ! [ Navigation.newUrl path ]
RedirectToLogin ->
model ! [ Navigation.load "path/to/login" ]
-- ExpenseList/Msg.elm
type ExpenseListMsg
= GetExpensesProgress (Progress (Api.Data (List Expense)))
| SetLoading
-- ExpenseList/Update.elm
update : ExpenseListMsg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
SetLoading ->
{ model | expenses = setExpensesLoading model.expenses } ! []
GetExpensesProgress (Done { data }) ->
{ model | expenses = addExpenses model.expenses data } ! []
GetExpensesProgress (Fail (BadStatus { status })) ->
case status.code of
401 ->
model ! [ msgToCmd RedirectToLogin ]
_ ->
model ! []
GetExpensesProgress (Fail error) ->
model ! []
GetExpensesProgress progress ->
{ model | expenses = setExpensesLoading model.expenses } ! []
Essentially, I want to move the logic around 401 responses from ExpenseList/Update.elm up to Main/Update.elm so that I can use it for any request I want.
I attempted a number of things, but nothing quite worked with Elm's type system. For example, one thing I wanted to do was try to do a nested pattern match with missing specificity in the middle, e.g.:
-- Main/Update.elm
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ApiCall (messageType (msg (Fail (BadStatus { status })))) ->
case status of ->
. . .
. . .
I was hoping something like this would work and would match a message that looked like: ApiCall (ExpenseListMsg (GetExpensesProgress (Fail (BadStatus)))). Unfortunately, it's not proper Elm syntax, so my code didn't compile.
How can I write something that will allow me to mark an API call as protected and catch 401 errors at the top level in Main.Update.update?
Currently, the API call is encapsulated by the ExpenseList/Update module. This encapsulation is what makes the API call results unavailable to the Main module. The interaction works like this: Main -> FeatureModule -> API
Because the API is what provides the information needed to determine whether the app should redirect to the login page, and you want the Main module to perform the redirect, the Main module needs access to the API. Hence, the encapsulation needs to go. Instead, you can:
Have an API module which provides the low-level API functionality by producing Tasks. Unlike producing Cmds, this allows the caller, such as the Main module to decide how to handle the result of the Task, which can be acquired by converting the Task to a Cmd and giving it to the Elm runtime for execution.
Have the ExpenseList.Update module use the API module to create Tasks.
With this arrangement:
The Main module sends high-level commands to a feature module, which then uses the API module to produce the low-level instructions, which are then provided to the Main module.
The Main module doesn't need to care what those low-level instructions are, it simply converts the Task to a Cmd and waits for the result.
When the result comes back it's in a low-level format (ex. Success/Fail). At this point the Main module can jump in and handle the redirect on a 401 error. Otherwise, it can pass the result to a feature module so it may handle the result.

How to use ports inside StartApp in Elm

In my app that based on the StartApp package I have a port to communicate from inside the to JS. At the moment I call this port using a mailbox
requestPalette :
{ address : Signal.Address String
, signal : Signal String
}
requestPalette = Signal.mailbox ""
requestPaletteFilter : Signal String
requestPaletteFilter =
Signal.filter (String.isEmpty >> not) "" requestPalette.signal
|> settledAfter (300 * Time.millisecond)
port request : Signal String
port request = requestPaletteFilter
and using it like this:
[on "input" targetValue (\str -> Signal.message requestPalette.address str)
I wonder if there is a way to this inside of the update function instead of sending the message from the view.
This applies to elm 0.16 (and before), in elm 0.17 subscriptions have changed into ports
In order to send a signal to a mailbox from an update, you'll need to use StartApp as opposed to StartApp.Simple, since the former allows for Effects in the update function.
At a bare minimum, you're going to probably have an Action like this, which defines a No-Op and an action for sending the string request:
type Action
= NoOp
| SendRequest String
Your update function will now include something like the following case for the new SendRequest action. Since you're using StartApp, which deals in Effects, you must call Effects.task, and the Task you're mapping to an Effect must be of type Action, which is why we have the Task.succeed NoOp return value.
update action model =
case action of
NoOp ->
(model, Effects.none)
SendRequest str ->
let
sendTask =
Signal.send requestPalette.address str
`Task.andThen` (\_ -> Task.succeed NoOp)
in
(model, sendTask |> Effects.task)
Now your click event handler in the view can go back to using the address passed into the view:
[ on "input" targetValue (Signal.message address << SendRequest) ]
I've got a working example of the above in this gist. You'll just need to subscribe to the request port in javascript to see it in action.