Testing Post requests in Ktor - http

Ktor (kotlin web framework) has an awesome testable mode where http requests can be wrapped in unit tests. They give a nice example of how to test a GET endpoint here,
however I'm having trouble with an http POST.
I tried this but the post params don't seem to be added to the request:
#Test
fun testSomePostThing() = withTestApplication(Application::myModule) {
with(handleRequest(HttpMethod.Post, "/api/v2/processing") {
addHeader("content-type", "application/x-www-form-urlencoded")
addHeader("Accept", "application/json")
body = "param1=cool7&param2=awesome4"
}) {
assertEquals(HttpStatusCode.OK, response.status())
val resp = mapper.readValue<TriggerResponse>(response.content ?: "")
assertEquals(TriggerResponse("cool7", "awesome4", true), resp)
}
}
Anyone have any ideas?

For those using the alternate .apply to verify results, you can prepend the body before the test call
withTestApplication({ module(testing = true) }) {
handleRequest(HttpMethod.Post, "/"){
setBody(...)
}.apply {
assertEquals(HttpStatusCode.OK, response.status())
assertEquals("HELLO WORLD!", response.content)
}
}

Ok dumb mistake, I'll post it here in case this saves somebody else from wasting time ;)
The unit test was actually catching a real problem (thats what they're for I guess)
In my routing I was using:
install(Routing) {
post("/api/v2/processing") {
val params = call.parameters
...
}
}
However that only works for 'get' params. Post params need:
install(Routing) {
post("/api/v2/processing") {
val params = call.receive<ValuesMap>()
...
}
}

For those reading it nowadays, back in 2018 receiveParameters() method was added for such cases. You can use it as:
install(Routing) {
post("/api/v2/processing") {
val params = call.receiveParameters()
println(params["param1"]) // Prints cool7
...
}
}
Also it's worth noting that request construction in the example could be further improved nowadays:
// Use provided consts, not strings
addHeader(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString())
// Convenient method instead of constructing string requests
setBody(listOf("param1" to "cool7", "param2" to "awesome4").formUrlEncode())

call.parameters also works for post routes.
get("api/{country}") {
val country = call.parameters["country"]!!
...
}
This will give you whatever is passed in the uri after api.
call.receive is for the body of a request.

Related

Inject headers into httptest.Recorder so echo context can see them in Golang

I have some tests that inject headers into Echo like this:
func test() {
request := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
recorder := httptest.NewRecorder()
recorder.HeaderMap.Add("If-None-Match", "\"d41d8cd98f00b204e9800998ecf8427e\"")
context := inst.NewContext(request, recorder)
testFunc(context)
fmt.Printf("Status: %d", context.Response().Status)
}
func testFunc(ctx echo.Context) {
ifNoneMatch := ctx.Response().Header().Get(headers.IfNoneMatch)
if !util.IsEmpty(ifNoneMatch) && etag == ifNoneMatch {
ctx.Response().WriteHeader(304)
}
}
My current solution works but, as HeaderMap is deprecated, I'm trying to find a better way to do this. I've tried injecting the header into Result by doing Result().Header.Add("If-None-Match", "\"d41d8cd98f00b204e9800998ecf8427e\"") but it doesn't seem to show up when calling context.Response().Header(). Is there any way to do this?
Instead of using HeaderMap() which is deprecated, you can use this:
request.Header().Set("Header-name", "Any header")

Cloudflare Workers - changes are not visible on live (but are in preview)

Hello and thank you for your help.
Sadly support over at CF does not think they need to help me.
I am learning to use workers, and have written a simple HTML injector just to see it working on my site.
this is the full worker code i have:
async function handleRequest(req) {
const res = await fetch(req)
const contentType = res.headers.get("Content-Type")
console.log('contentType: ', contentType)
// If the response is HTML, it can be transformed with
// HTMLRewriter -- otherwise, it should pass through
if (contentType.startsWith("text/html")) {
return rewriter.transform(res)
} else {
return res
}
}
class UserElementHandler {
async element(element) {
element.before("<div class='contbox'><img src='https://coverme.co.il/wp-content/uploads/2020/01/covermeLOGO-01-1024x183.png' style='width:200px;margin:20px;'><h1>testing inserting</h1></div>", {html: true});
// fill in user info using response
}
}
const rewriter = new HTMLRewriter()
.on("h1", new UserElementHandler())
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
})
it just uses element.before to inject some HTML.
in the worker preview pane i can see it!
but on the live site = nothing.
this is the active URL: [https://coverme.co.il/product/%D7%A0%D7%A8-%D7%91%D7%99%D7%A0%D7%95%D7%A0%D7%99-tuberosejasmine/]
these are the 4 routes i have set up to try to catch this, with and without encoding the letters:
coverme.co.il/product/נר-בינוני-tuberosejasmine/
*.coverme.co.il/product/נר-בינוני-tuberosejasmine/*
https://coverme.co.il/product/%D7%A0%D7%A8-%D7%91%D7%99%D7%A0%D7%95%D7%A0%D7%99-tuberosejasmine/
*.coverme.co.il/product/%D7%A0%D7%A8-%D7%91%D7%99%D7%A0%D7%95%D7%A0%D7%99-tuberosejasmine/*
thanks in advance!
I believe the problem here is that you've configured your routes to match "נר-בינוני" unescaped, but the browser will actually percent-encode the URL before sending to the server, therefore the route matching actually operates on percent-escaped URLs. So the actual URL is https://coverme.co.il/product/%D7%A0%D7%A8-%D7%91%D7%99%D7%A0%D7%95%D7%A0%D7%99-tuberosejasmine/, and this does not match your route because %D7%A0%D7%A8-%D7%91%D7%99%D7%A0%D7%95%D7%A0%D7%99 is not considered to be the same as נר-בינוני.
EDIT: Unfortunately, using percent-encoding in your route pattern won't fix the problem, due to a known bug. Unfortunately, it's just not possible to match non-ASCII characters in a Workers route today. We intend to fix this, but it's hard because some sites are accidentally dependent on the broken behavior, so the fix would break them.
What you can potentially do instead is match against coverme.co.il/product/*, and then, inside your worker, check if the path also has נר-בינוני-tuberosejasmine. If it does not, then your fetch event handler should simply return without calling event.respondWith(). This will trigger "default handling" of the request, meaning it will pass through and be sent to your origin server like normal. (Note that you will still be billed for a Workers request, though.)
So, something like this:
addEventListener("fetch", event => {
if (event.request.url.includes(
"coverme.co.il/product/נר-בינוני-tuberosejasmine/")) {
event.respondWith(handle(event.request));
} else {
return; // not a match, use default pass-through handling
}
})

Beego POST request body always empty

I'm working with Beego's convenience methods for parsing request body values and have the following:
Router file:
apiNamespace := beego.NewNamespace("/api")
apiNamespace.Router("/sessions/google/new", &controllers.SessionsController{}, "get:GoogleNewSession")
beego.AddNamespace(apiNamespace)
Controller code:
func (c *SessionsController) URLMapping() {
c.Mapping("GoogleNewSession", c.GoogleNewSession)
}
func (c *SessionsController) GoogleNewSession() {
// Always serve JSON
defer func() {
c.ServeJson()
}()
// This is always blank
log.Printf("'Received %+v'", c.Ctx.Input.RequestBody)
c.Ctx.ResponseWriter.WriteHeader(200)
return
// truncated
}
Front end JS (super-agent):
request
.post('/sessions/google/new')
.use(prefix)
.send({ code: authCode })
.set('Accept', 'application/json')
.end(function(err, res){
console.log("******* request", res.request)
if (res.ok) {
var body = res.body;
console.log('yay got ' + JSON.stringify(res.body));
} else {
console.log("***** err", err);
console.log("***** not ok", res.text);
}
});
When the superagent request fires off, I can see in the logs that the path is getting correctly matched. However, the c.Ctx.Input.RequestBody is always empty.
I have tried using something else to fire the request such as Postman but to no avail. In GET requests I am able to retrieve query params correctly.
Any clues or suggestions to help fix or debug this issue?
You need to configure "copyrequestbody = true" in configuration file "conf/app.conf".
The default is false so the content is not copied to c.Ctx.Input.RequestBody.
The example is shown section "Retrieving data from request body" in the document. (http://beego.me/docs/mvc/controller/params.md)

Breeze query error, even though results returned

Breeze is calling the "fail()" function, even though the data seems to be returned from the odata service (as well as being in the error object). There are 5 "transactions" returned from the ODATA service (as seen in Chrome developer tools) as well as in the "data" property of the error object being passed to the fail function.
Calling code looks like this:
function getTransactions() {
var query = breeze.EntityQuery.from("Transactions")
.take(5);
return entityManager.executeQuery(query,
function(data) {
log("transaction Query success!");
var transactions = data.results;
},
function(err) {
log("Query failed:" + err.message);
});
}
I am at a loss as to what is wrong that is causing the "fail()."
There IS a Transaction constructor defined, code below:
function registerTransactions(metadataStore) {
metadataStore.registerEntityTypeCtor('Transaction', Transaction);
// constructor -- empty
function Transaction() { };
Object.defineProperty(Transaction.prototype, 'itemCount', {
get: function () {
return 0;
}
});
}
Note the url for the odata resource is "Transactions" but the entity is Transaction. What are the reasons why the "Fail() function would be called?
Error.message = "; " which isn't helping much.
I believe I am on the latest Breeze 1.4.11 and datajs 1.1.2
After much research, I found the problem was another funcky CORS setting on the service side. I was able to figure it out by going directly to dataJS against the same service, and getting a more informative error message.
What you MUST do on the service side is something like this:
var cors = new EnableCorsAttribute("*", "*", "*", "DataServiceVersion, MaxDataServiceVersion");
The last parameter has to with the service sending the OData version in the header and thereby allowing the client to determine if it can handle the specified version of OData.
If anyone knows more details about this, feel free to comment.

Scalatra Basic Authentication for part of an application

I'm trying to figure out how to write an app which has basic authentication enabled for certain URLs. The authenticated part should not have form-based authentication, just the default login I can do easily from Javascript/JQuery. I've seen a few examples which look complicated and when I try to use them, lot of the stuff is deprecated and in general it seems to be a lot of work to get the example code even to compile now.
So are those examples still the best Scalatra has to offer or is there a simpler way now?
I'm using Scalatra (with scalatra-auth) version 2.1.1.
Found an easier example and got the below code working.
package mc.nulty
import org.scalatra.auth.strategy.BasicAuthStrategy.BasicAuthRequest
import org.scalatra._
import scalate.ScalateSupport
class McNultyServlet extends ScalatraServlet with ScalateSupport {
get("/") {
basicAuth
<html>
<body>
<h1>Hello, world!</h1>
Say hello to Scalate.
</body>
</html>
}
notFound {
// remove content type in case it was set through an action
contentType = null
// Try to render a ScalateTemplate if no route matched
findTemplate(requestPath) map { path =>
contentType = "text/html"
layoutTemplate(path)
} orElse serveStaticResource() getOrElse resourceNotFound()
}
protected def basicAuth() = {
val req = new BasicAuthRequest(request)
def notAuthenticated() {
response.setHeader("WWW-Authenticate", "Basic realm=\"%s\"" format "mc-nulty")
halt(401, "Unauthenticated")
}
if(!req.providesAuth) {
notAuthenticated
}
if(!req.isBasicAuth) {
halt(400, "Bad Request")
}
val user = DAO.validateLoginPassword(req.username, req.password)
if (user != null)
response.setHeader("REMOTE_USER", "user.id")
else {
notAuthenticated
}
Option(user)
}
object DAO {
def validateLoginPassword(username: String, password: String) : User = {
if (username.equals("foo")) new User()
else null
}
}
class User(val id:String = "dummyid") {}
}
There's now a Scalatra guide on authentication which covers the basic auth case you're looking for. See http://scalatra.org/2.2/guides/http/authentication.html
Scalatra's auth integrations should not have changed between Scalatra 2.1.1 (which you're using) and the soon-to-be-released Scalatra 2.2.0, so the guide should still be valid for you.

Resources