I'm working on a test case wherein I want to check the integrity of the argument pass to the method.
Here how my controller code looks like.
def show
response = begin
site = Site.find_by(id: params[:id])
site.bonsai_site_configuration
rescue StandardError => exception
ExceptionNotifier.notify_exception(exception)
{}
end
render json: response, status: 200
end
Now, all I want to ensure that when I mock the find_by and to return site object I need the argument to match exactly what I intend it to be.
over here {id: site.id}
This is how my test currently looks like. I'm not very much familiar with minitest so would request you to please have patience with me. As I'm not entirely sure if the below code(test) is the correct way to write a minitest test.
test 'Services::SiteController: Show: Return `{}` as Site configuration on error' do
mock = MiniTest::Mock.new
mock.expect :call, true, [RuntimeError]
site = Site.first
site_mock = MiniTest::Mock.new
site_mock.expect :call, site, [{id: site.id}]
raises_exception = Proc.new { raise 'boom' }
Site.stub(:find_by, site_mock) do
site.stub(:bonsai_site_configuration, raises_exception) do
ExceptionNotifier.stub(:notify_exception, mock) do
get "/switch/sites/#{site.id}",
headers: { Authorization: "Basic b2576d705c208c19fd1601ef58aa0506bd4e2e93c51c19df856483cbbfc7cb67" }
response = JSON.parse(#response.body)
assert_successful_response
assert_empty(response)
end
end
end
assert_mock site_mock
assert_mock mock
end
Ok, there are 2 ways to test this.
Firstly, I was correct the
site_mock.expect :call, site, [{id: site.id}]
is the correct way to test this behaviour all I needed to change is.
site_mock.expect :call, site, [{id: site.id.to_s}]
The other way to test this is.
site_mock.expect :call, site do |*args|
args[0][:id] == site.id
end
Related
I'm using loopback 3.
I have this lines of codes in my model's js (survey.js):
let enabledRemoteMethods = []
Survey.sharedClass.methods().forEach(function(method) {
console.log(method.name, method.isStatic)
let matchingEnabledRemoteMethod = _.find(enabledRemoteMethods, {name: method.name});
if (!matchingEnabledRemoteMethod) {
Survey.disableRemoteMethodByName(method.name, method.isStatic);
}
});
It works.... almost. I could still see in the explorer the REST endpoint for "PATCH /surveys/{id}". My expectation is: there shouldn't be any REST endpoints listed in the explorer.
Then I examined the URL corresponding to that operation, it is:
http://localhost:3000/explorer/#!/Survey/Survey_prototype_patchAttributes
Which, according to the documentation, means that patchAttributes is a static method.
Then I cross checked with the output in the console... there it says: pathAttributes is not static.
Incosistency!
I even have tried adding this line:
Survey.disableRemoteMethodByName("patchAttributes", true);
Also
Survey.disableRemoteMethodByName("patchAttributes", false);
No luck.
Can someone confirm if it's a bug in loopback 3 (I don't know about loopback 2, haven't checked)? If it's a bug I wouldn't have to spend time on it and just wait until it gets fixed. But if it's not a bug, can someone point out what's missing in my code?
Thanks!
UPDATE: figured out how
With this line I'm able to get rid of it:
Survey.disableRemoteMethodByName("prototype.patchAttributes", true);
The second parameter doesn't seem to matter (you can put false as well). Not sure why though (I suppose it should've accepted true only).
This is my current solution:
let disabledPrototypesRemoteMethods = ['patchAttributes']
let enabledRemoteMethods = [
"create", "findById", "replaceById", "deleteById",
"replaceOrCreateQuestion"
]
Survey.sharedClass.methods().forEach(function(method) {
if (enabledRemoteMethods.indexOf(method.name) == -1) {
Survey.disableRemoteMethodByName(method.name);
}
if (disabledPrototypesRemoteMethods.indexOf(method.name) > -1) {
Survey.disableRemoteMethodByName("prototype." + method.name);
}
});
Still, one small detail: this thing still shows up (I suppose it provides the POST alternative for the normal PUT for the replaceById operation..., but I don't want it; I want to force user of my API to go with the PUT only):
http://localhost:3000/explorer/#!/Survey/Survey_replaceById_post_surveys_id_replace
I tried adding this line:
Survey.disableRemoteMethodByName("replaceById_post_surveys_id_replace");
No luck.
Anyway... hope this useful for others; loopback doc is kind of sketchy.
You can get the prototype methods as well by looking at the stringName property. That way you can include the prototypes in your list.
The stringName includes the sharedClass name in the value, so you will need to parse that out.
module.exports = BusinessProfileContacted => {
const enabledRemoteMethods = ["create", "findById", "replaceById", "deleteById", "replaceOrCreateQuestion", "prototype.replaceAttributes"];
Survey.sharedClass.methods().forEach(method => {
const methodName = method.stringName.replace(/.*?(?=\.)/, '').substr(1);
if (enabledRemoteMethods.indexOf(methodName) === -1) {
Survey.disableRemoteMethodByName(methodName);
}
});
};
I am writing a test driven development for my strongloop API code with the help of loopback-testing .
Here they do not have any detailed document on this, so i am stuck with case of argument passing with the API call
Example i have a below case,
Method : PUT
URL : /api/admin/vineyard/<vineyard_id>
i need to pass the below arguments with this URL
1. 'vineyard_id' is a id of vine, it should be an integer .
2. in header = 'token'
3. in body = '{'name':'tastyWine','price':200}'
How can i pass these three arguments with this API ?
I can easily handle ,if there is only two types of arguments
Example :
Method : POST
`/api/user/members/<test_username>/auth'`
arguments : test_username and password
I can handle this like this ,
lt.describe.whenCalledRemotely('POST',
'/api/user/members/'+test_username+'/auth', {
'password': test_passwords
},
But how can i handle the above case , Many thanks for your answers for this example.
I'm not entirely sure what your specific problem is, but I will attempt to walk through everything you should need.
I am assuming you are using the predefined prototype.updateAttributes() method for your model as described here.
Next assumption is that you want to use the built-in authentication and authorization to allow the user to call this method. Given that assumption, you need something like this in your test code:
var vineyard_id = 123; //the id of the test item you want to change
var testUser = {email: 'test#test.com',password: 'test'};
lt.describe.whenCalledByUser(testUser, 'PUT', '/api/admin/vineyard/'+vineyard_id,
{
'name':'tastyWine',
'price':200
},
function () {
it('should update the record and return ok', function() {
assert.equal(this.res.statusCode, 200);
});
}
);
If you are using the out-of-the-box user model, you should be fine, but if you extended the model as is commonly done, you may need something like this early on in your test file:
lt.beforeEach.withUserModel('user');
Also, be aware of a few (currently incomplete) updates to will allow for better handling of built-in model extensions: Suggestions #56, Add support for non-default models #57, and givenLoggedInUser() function throws error #59.
How do you deal with the fact, that URLs are case sensitive in xPages even for parameters? For example URL:
my_page.xsp?folderid=785478 ... is not the same as ...
my_page.xsp?FOLDERID=785478
How to make, for example, a proper check that params contain some key e.g.
param.containsKey("folderid") which desnt work when there is 'FOLDERID' in URL.
I'd suggest defining a couple convenience #Functions:
var #HasParam = function(parameter) {
var result:boolean = false;
for (var eachParam : param.keySet()) {
if (eachParam.toLowerCase() == parameter.toLowerCase()) {
result = true;
break;
}
}
return result;
};
var #GetParam = function(parameter) {
var result = "";
if (#HasParam(parameter)) {
for (var eachParam : param.keySet()) {
if (eachParam.toLowerCase() == parameter.toLowerCase()) {
result = param.get(eachParam);
break;
}
}
}
return result;
};
Then you can safely query the parameters without caring about case. For bonus points, you could add requestScope caching so that you can skip looping through the keySet if you're examining a parameter that you've previously looked at during the same request.
you may use this function:
context.getUrlParameter('param_name')
then test if it's null or not.
make sure to decide for one,so either upper or lowercase
other than that i'd suggest something like
KeyValuePair<string,string> kvp = null;
foreach(KeyValuePair<string,string> p in param)
{
if(UPPERCASE(p.Key) == UPPERCASE("folderid"))
{
kvp = p;
break;
}
}
syntax isn't correct and idk the uppercase method in c# right now,but you get the point
The easiest answer is ofcourse the obvious. Be sure that the parameters you are using througout your application are always the same on every url you are generating and know what to expect. A good approach to accomplish this is to create a ssjs function which generates url's for you according to the objects you submit.
In this function you could check which object you are receiving and with the use of keywords and so forth generate the correct url. This way generating twice a url with the same input parameters should always generate the exact same url.
another option would be just to double check with a bit of code like this
var key = "yourkey";
if(param.contains(#uppercase(key)) || param.contains(#lowercase(key)){
// do stuff
}
But should not be necesarry if the url you are parsing is generated by your own application
Edit after post of topic starter
Another option would be to grap the url directly from from the facescontext and to convert it to a string first. When it is a string you can parse the parameters yourself.
You can combine server side substitution/redirection to get around the issue that David mentioned. So a substitution rule will redirect incoming patern like this:
http://myhost/mypage/param (/mypage/* => which converts to - /dbpath/mypage.xsp?*) - substitution is tricky so please handle with care.
Also I believe I read somewhere that context.getUrlParameter is not case sensitive - can someone please confirm this.
Hope this helps.
What does def edit = {} contain by default? You see, I was following a book but it turns out to be using an older version that's why some of the code don't work. I have this piece of code:
def edit= {
def user = User.get(params.id)
if (session?.user?.id == null){
flash.message = "You have to login first before editting your stuff."
redirect(action:'login')
return
}else if(session?.user?.id != params.id) {
flash.message = "You can only edit yourself."
redirect(action:list)
return
}else{
//What should I put here?
}
}
It's already functional. If the user clicks on edit without logging in, then he's redirected to a login page. Otherwise, if he did login, then he's only allowed to edit himself. What should I put on the "else" clause? It should already should already allow the user to edit his stuff, but I don't really know how to implement what I want. :(
It would be great if someone could share the default edit snippet.
I'm a bit new to all these, so go easy on me.
If you're talking about Grails, back up your UserController and try grails generate-controller - it will give you the complete text of default actions.
I also suggest that you look through scaffolding chapter - it's a great point to start.
the default edit action should look like this (pseudo-code, it depends on the actual domain class you create the code upon):
def edit = {
redirect(action: "show", id: params.id)
return true
def <domain>Instance = <DomainClass>.get(params.id)
if (!<domain>Instance) {
flash.message = "${message(code: 'default.not.found.message', args: [message(code: '<DomainClass>.label', default: '<DomainClass>'), params.id])}"
redirect(action: "list")
}
else {
return [<domain>Instance: <domain>Instance]
}
}
btw: most of the time you don't have to do the security checks by programming these explicitly in the controller code, check out the Grails Spring Security Plugin for that purpose.
In my controller, I have a before() function that calls parent::before() and then does some additional processing once the parent returns. based on a specific condition, I want to "save" the original request and pass execution to a specific action. Here is my before() function.
public function before() {
parent::before();
$this->uri = Request::Instance()->uri;
$match = ORM::factory('survey_tester')
->where('eid','=',$this->template->user->samaccountname)
->find();
if (!$match->loaded()) {
self::action_tester("add",$this->template->user);
}
}
And the action that is being called..
public function action_tester($op=null,$user=null) {
$testers = ORM::factory('survey_tester')->find_all();
$tester = array();
$this->template->title = 'Some new title';
$this->template->styles = array('assets/css/survey/survey.css' => 'screen');
$this->template->scripts = array('assets/js/survey/tester.js');
$tester['title'] = $this->template->title;
$tester['user'] = $this->template->user;
switch ($op) {
case "add":
$tester = ORM::factory('survey_tester');
$tester->name = $user->displayname;
$tester->email = $user->mail;
$tester->division = $user->division;
$tester->eid = $user->samaccountname;
if ($tester->save()) {
$this->template->content = new View('pages/survey/tester_add', $admin);
} else {
$this->template->content = new View('pages/survey/tester_error', $admin);
}
break;
default:
break;
}
}
This all seems to work fine. This is designed to prompt the user for a specific piece of information that is not provided by $user (populated by LDAP) if this is the first time they are hitting the controller for any reason.
The problem is the views are not rendering. Instead control passes back to whatever action was originally requested. This controller is called survey. If i browse to http://my.site.com/survey and login with new user info, the record gets written and i get the action_index views instead of my action_tester views.
I cannot figure out what I am doing wrong here. Any ideas will be appreciated. Thank you.
EDIT: I managed to get this working (sort-of) by using $this->request->action = 'tester'; but I'm not sure how to add/set new params for the request yet.
The issue is that you are calling your method (action_tester), but then Kohana is still going to call the original action after the before method is called, which is going to change the response content overwriting the changed made in action_tester().
You can change the action being called (after before is called) inside your before() method:
$this->request->action('action_tester');
After the before method is called, it should then call the new Action (action_tester) rather than the old one, but then you need to do something about the way you are passing your parameters then.
Or you could just redirect the request upon some condition:
if($something) {
$this->request->redirect('controller/tester');
}
This doesn't seem like a nice way to do it anyway.