Using UnitTest for a RestService
I am trying to use the automated unittest class for a %CSP.Rest service.
So my Unittest code set's %request and %response from %CSP.Req/resp respectively
I build a tProxy with the fields I need for the post and set it %ToJson... I have tried seting %request.Content = tProxy (and not)
I call the method for the post url directly...
When that method calls %fromJson to set an object with the info. I passed...
- when I don't set %request.content.. I get a error '5035 - premature end of data code 12'
- when I set %request.content=tProxy.. I get an errror Method Does not exist
Any tips on what may be going on?
%request.content is an object of type %CSP.Stream;
http://docs.intersystems.com/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25CSP.Request#PROPERTY_Content
so you have to access it by Stream Methods. Simple SET either left or right side doesn't work.
see docs:
http://docs.intersystems.com/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25CSP.Request
Gordon,
Thanks for your reply.. after hours of researching what Robert posted, I could not get it to work either. I thought I had it solved by instantiating a new object content=##Class(%Library.Stream.Object).%New()... and building that.. then setting into %Request.Content=Content but it doesn't like that either.. so I am at a loss as well.
George
Worked great Sean.. thank you!
George, Gordon
Interesting info on Upgrade2016.1 This seems to by some fake news.
Since the class is there om 2017.2 and even in IRIS2018.1
http://docs.intersystems.com/iris20181/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25CSP.Stream
The Type set fails because %CSP.Stream is just an abstract class.
Like %CSP.Page you have to create the real object yourself as %CSP.CharacterStream or %CSP.BinaryStream
a little bit simplified for retyping in terminal:
set cont=req.Content zw cont ; MO AUTOMATIC OBJECT BEAUSE ABSTRACT !!
cont=""
set cont=##class(%CSP.CharacterStream).%New() zw cont ; NOW WE HAVE AN OBJECT
cont=<OBJECT REFERENCE>[5@%CSP.CharacterStream]
+----------------- general information ---------------
| oref value: 5
| class name: %CSP.CharacterStream
| reference count: 2
+----------------- attribute values ------------------
| (%Concurrency) = 1
| (%LastModified) = ""
| %Location = "" <Get,Set>
| (%LockRef) = ""
| (%Locked) = 0
| AtEnd = 0
| (Buffer) = ""
| (IOSize) = 0
| Id = ""
| LineTerminator = $c(13,10) <Set>
| (MaxNodeNo) = 0
| (Mode) = 0
| (NodeNo) = 0
| (Position) = 1
| (StoreNode) = ""
| (StoreRoot) = "^CacheStream"
| (TempNode) = ""
+--------------- calculated references ---------------
| CharSet <Get,Set>
| ContentType <Get,Set>
| Expires <Get,Set>
| FileName <Get,Set>
| Headers <Get,Set>
| LastModified <Get>
| MimeSection <Get,Set>
| Size <Get>
| (StoreGlvn) <Get>
| (TempGlvn) <Get>
+-----------------------------------------------------
set cont.ContentType="application/json"
; and so on ....
HTH
Hi George,
I'm most interested in your Question on Unit Testing a REST service.
The approach I've taken seems similar to yours. Unfortunately I've not yet cracked a working Unit Test implementation for this scenario.
Equally I had already studied the documentation that 'Robert Cemper' suggested in his answer links. Also I note in the following link:
http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=...
that a bunch of the %CSP.Stream methods are being deprecated from 2016.1 on. I have been unable to locate their replacement or alternative approach.
If you, Robert or anyone in the community can supply a working code sample of how to set the parameters of a %CSP.Request object would be much appreciated.
The following code compiles but fails on execution.
#dim status As %Status
#dim cspRequest As %CSP.Request = ##class(%CSP.Request).%New()
set status = cspRequest.Content.ContentTypeSet("application/json") => Fails => OREF Failure => I assume to do with %CSP.Stream
My aim with the Unit Test is to somehow have the class that Extends %CSP.REST be instansiated or ingest the cspRequest As %CSP.Request. This would allow for directly debugging the ClassMethods of the %CSP.REST class.
Much appreciate your assistance.
Cheers,
Gordon
Hi George / Gordon,
Feels like too many potential trip hazards getting this right, and the possibility of creating false positives.
I would recommend using an HTTP client instead.
Here is a quick and dirty example, it uses %Net.HttpRequest to make the REST calls. Take a look at the documentation here http://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls as there are many other settings (e.g. credentials) that you might also want to set.
The example has both a GET and a POST...
{
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/book" Method="POST" Call="PostBook" />
<Route Url="/book/:ibn" Method="GET" Call="GetBook" />
</Routes>
}
ClassMethod PostBook() As %Status
{
set obj=##class(%DynamicObject).%FromJSON(%request.Content)
set ^Foo.Books(obj.ibn,"name")=obj.name
set ^Foo.Books(obj.ibn,"author")=obj.author
quit 1
}
ClassMethod GetBook(pIBN) As %Status
{
if '$Data(^Foo.Books(pIBN)) set %response.Status = 404 return 1
set book = {
"ibn" : (pIBN),
"name" : (^Foo.Books(pIBN,"name")),
"author" : (^Foo.Books(pIBN,"author"))
}
write book.%ToJSON()
return 1
}
ClassMethod GetNewRestHelper() As %Net.HttpRequest
{
set httpRequest=##class(%Net.HttpRequest).%New()
set httpRequest.Server="localhost"
set httpRequest.Port=57772
set httpRequest.Timeout=2
quit httpRequest
}
ClassMethod Tests() As %Boolean
{
//basic assert, replace with actual unit tester implementation
#define assert(%a,%b) write !,"Test ",$increment(count)," ",$select(%a=%b:"PASSED",1:"FAILED, expected """_%b_""" recieved"""_%a_"""")
//TEST 1 : should save ok
set rest=..GetNewRestHelper()
set book = {
"ibn" : 1234,
"name" : "Lord Of The Rings",
"author" : "J.R.R. Tolkien"
}
set json = book.%ToJSON()
do rest.EntityBody.Write(json)
do rest.Post("/foo/book")
$$$assert(200,rest.HttpResponse.StatusCode)
//TEST 2 : should find book 1234
set rest=..GetNewRestHelper()
do rest.Get("/foo/book/1234")
$$$assert(200,rest.HttpResponse.StatusCode)
set json=rest.HttpResponse.Data.Read(32000)
set book=##class(%DynamicObject).%FromJSON(json)
$$$assert(book.ibn,1234)
$$$assert(book.name,"Lord Of The Rings")
$$$assert(book.author,"J.R.R. Tolkien")
//TEST 3 : should NOT find book 5678, test should fail
set rest=..GetNewRestHelper()
do rest.Get("/foo/book/5678")
$$$assert(200,rest.HttpResponse.StatusCode)
}
}
Hi George, Robert, Sean,
Thanks all for responding so promptly.
All comments have combined into what I needed...
Cheers,
Gordon
Code follows:
{
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/(.*)" Method="GET" Call="RestApi.Ris:ProcessUrl"/>
<Route Url="/(.*)" Method="POST" Call="RestApi.Ris:ProcessUrl"/>
<Route Url="/(.*)" Method="PUT" Call="RestApi.Ris:ProcessUrl"/>
<Route Url="/(.*)" Method="DELETE" Call="RestApi.Ris:ProcessUrl"/>
</Routes>
}
ClassMethod ProcessUrl(capturedUrlData As %String = "", debugCspRequest As %CSP.Request = "") As %Status
{
#dim %request As %CSP.Request
#dim %response As %CSP.Response
if ((debugCspRequest '= "") && ($IsObject(debugCspRequest)) && ($ClassName(debugCspRequest) = "%CSP.Request"))
{
set %request = debugCspRequest
set %response = ##class(%CSP.Response).%New()
}
Method Implementation continues...
{
/*
Method OnBeforeOneTest() As %Status
{
/// Code to run Before Test/s
///
/// Quit $$$OK
}
*/
ClassMethod MockCspRequest(requestMethod As %String = "", requestUri As %String = "", jsonPayload As %String = "", authorizationToken As %String = "") As %Net.HttpRequest
{
#dim status As %Status
#dim cspRequest As %CSP.Request = ##class(%CSP.Request).%New()
set cspRequest.CharSet = "UTF-8"
if (('$Data(requestMethod)) || ($IsObject(requestMethod)) || (requestMethod = "")) return ""
if (('$Data(requestUri)) || ($IsObject(requestUri)) || (requestUri = "")) return ""
if (('$Data(jsonPayload)) || ($IsObject(jsonPayload)) || ($zStrip(jsonPayload, "<>", " ") = "")) set jsonPayload = ""
if (('$Data(authorizationToken)) || ($IsObject(authorizationToken)) || ($zStrip(authorizationToken, "<>", " ") = "")) set authorizationToken = ""
if ((authorizationToken '= "") && ('##class(RestApi.Helpers.String).IsValidGuid(authorizationToken))) return ""
#dim patternRequestMethod as %String = "\A(?i)Get|Post|Put|Delete\z"
#dim regex as %Regex.Matcher = ##class(%Regex.Matcher).%New(patternRequestMethod, requestMethod)
#dim isMatch as %Boolean = regex.Locate()
if ('isMatch) return ""
// Request Method
set requestMethod = $zConvert(requestMethod, "U")
set cspRequest.CgiEnvs("REQUEST_METHOD") = requestMethod
set cspRequest.Method = requestMethod
// URI Endpoint validation
#dim patternEndpoint as %String = "(?i)\A(https?|ftp)://([-A-Z0-9.]+)(?::(\d{1,5}))?(/[-A-Z0-9+&@#/%=~_|!:,.;]*)?(\?[A-Z0-9+&@#/%=~_|!:,.;]*)?\z"
// Standard Naming (G1 Protocol) (G2 Domain ) (G3 Port) (G4 File ) (G5 QueryParameters )
// Caché Doco Naming (G1 Protocol) (G2 Server ) (G3 Port) (G4 Location ) (G5 QueryParameters )
set regex = ##class(%Regex.Matcher).%New(patternEndpoint, requestUri)
set isMatch = regex.Locate()
if ('isMatch) return ""
#dim protocol as %String = $zConvert(regex.GroupGet(1),"L")
#dim server as %String = regex.GroupGet(2)
#dim port as %String = regex.GroupGet(3)
#dim location as %String = regex.GroupGet(4)
#dim queryParams as %String = regex.GroupGet(5)
if (protocol = "ftp") return ""
if (port > 65535) return ""
set cspRequest.CgiEnvs("REQUEST_SCHEME") = protocol
set cspRequest.CgiEnvs("REQUEST_URI") = requestUri
set cspRequest.URL = location
if (authorizationToken '= "")
{
set cspRequest.CgiEnvs("HTTP_AUTHORIZATION") = authorizationToken
}
if ((jsonPayload '= "") && (requestMethod '= "GET"))
{
#dim cspStream As %CSP.Stream = ##class(%CSP.CharacterStream).%New()
set cspStream.CharSet = "utf-8"
set cspStream.ContentType = "application/json"
set status = cspStream.MimeSectionSet(jsonPayload)
set cspRequest.Content = cspStream
}
set queryParams = $zStrip(queryParams, "<>", " ")
if (queryParams = "") return cspRequest
#dim kvpData as %String = queryParams
#dim kvpElement as %String
#dim key as %String
#dim value as %String
#dim patternKvpElement as %String = "(?i)(\??(?:[a-z0-9]*?=[a-z0-9]*?)(?:&|\z))"
#dim patternKvp as %String = "(?i)(?:\??([a-z0-9]*?)=([a-z0-9]*?)(?:&|\z))"
set regex = ##class(%Regex.Matcher).%New(patternKvpElement, kvpData)
#dim isMatchKvpElement As %Boolean = regex.Locate()
while (isMatchKvpElement)
{
set kvpElement = regex.GroupGet(1)
set regex = ##class(%Regex.Matcher).%New(patternKvp, kvpElement)
set isMatch = regex.Locate()
if (isMatch)
{
set key = regex.GroupGet(1)
set value = regex.GroupGet(2)
Set cspRequest.Data("Name",$Order(cspRequest.Data(key, ""), -1) +1) = value
}
// Get Next KvpElement
set kvpData = $Extract(kvpData, ($Length(kvpElement) + 1), *-0)
if (kvpData '= "")
{
set regex = ##class(%Regex.Matcher).%New(patternKvpElement, kvpData)
set isMatchKvpElement = regex.Locate()
}
else
{
set isMatchKvpElement = 0
}
}
return cspRequest
}
Method TestProcessUrl()
{
// -------------------------------------------------------------------------------------------------------------------
// Arrange
#dim resultActual As %Status
#dim resultExpected As %Status
#dim status As %Status
#dim queryString As %String = "?qParam01=123456&qParam02=ABCDEF"
#dim mockUrl As %String = "http://1.2.3.4:80/SomeLocation"_queryString
#dim dq As %String = $Char(34)
#dim message As %String = "Unit Test REST API method without Web Server and complete line by line debug"
#dim jsonPayload As %String = "{"
set jsonPayload = jsonPayload_dq_"source"_dq_":"_dq_"Gordo"_dq _","
set jsonPayload = jsonPayload_dq_"urlMethod"_dq_":"_dq_"UnitTest"_dq _","
set jsonPayload = jsonPayload_dq_"text"_dq_":"_dq_message_dq
set jsonPayload = jsonPayload_"}"
#dim cspRequest As %CSP.Request = ..MockCspRequest("Post", mockUrl, jsonPayload)
Do $$$AssertTrue($IsObject(cspRequest), "cspRequest instance created")
#dim cspUrl As %String = cspRequest.URL
#dim cspMethod As %String = cspRequest.Method
#dim cspContentType As %String = cspRequest.ContentType
#dim patternRouteCaptue as %String = "/(.*)" // <Route Url="/(.*)"
#dim regex as %Regex.Matcher = ##class(%Regex.Matcher).%New(patternRouteCaptue, cspUrl)
#dim isMatch as %Boolean = regex.Locate()
Do $$$AssertTrue(isMatch, "Route has capturedUrlData")
#dim capturedUrlData As %String = regex.GroupGet(1)
// -------------------------------------------------------------------------------------------------------------------
// Act / Assert
set resultActual = ##class(RestApi.Ris).ProcessUrl(capturedUrlData, cspRequest)
Method Implementation continues...
Social networks
InterSystems resources
Log in or sign up
Log in or create a new account to continue
Log in or sign up
Log in or create a new account to continue
Log in or sign up
Log in or create a new account to continue