mocking a function call in midje - testing

Say I have a function
(defn extenal_api_fn [stuff]
... do things....
)
(defn register_user [stuff]
(external_api_fn stuff))
And then a test
(def stuff1
{:user_id 123 })
(def stuff2
{:user_id 234})
(background (external_api_fn stuff1) => true
(with-redefs [external_api_fn (fn [data] (println "mocked function"))]
(register_user stuff1) => true)
(register_user stuff2) => true)
(facts "stuff goes here"
(fact "user that registers correctly
(= 1 1) => truthy)
(fact "user that has a registration failure"
(= 1 2) => falsy))
This fails with
"you never said #'external_api_fn" would be called with these arguments:
contents of stuff1
What would be a good way to stub this function call (in only some cases) in order to simulate an internal transaction failure.

You could use Midje's provided:
(fact
(register_user stuff1) => :registered
(provided
(extenal_api_fn stuff1) => :registered))
(fact
(register_user stuff2) => :error
(provided
(external_api_fn stuff2) => :error))
You can also stub a function to return a value no matter input parameters by using anything in place of the function argument:
(fact
(register_user stuff2) => :error
(provided
(external_api_fn anything) => :error))

Related

link asymmetry in process ring

I'm currently working through the ubiquitous process ring in elixir.
The ring is linked, but in the following fashion:
iex(1)> Ring.Worker.create_ring_of_linked_processes(3)
Ring.Worker.create_ring_of_linked_processes(3)
[%{"links" => [#PID<0.121.0>, #PID<0.120.0>], "pid" => #PID<0.122.0>},
%{"links" => [#PID<0.120.0>, #PID<0.122.0>], "pid" => #PID<0.121.0>},
%{"links" => [#PID<0.121.0>], "pid" => #PID<0.120.0>}]
I've noticed an asymmetry in the links here - should #PID<0.120.0> have the mapping "links" => [#PID<0.121.0>,#PID<0.122.0>] rather than just "links" => [#PID<0.121.0>] ?
The code is as follows:
def loop() do
receive do
{:link, pid} when is_pid(pid) ->
Process.link(pid)
loop()
end
end
def create_ring_of_linked_processes(num_of_processes) do
num_of_processes
|> create_processes
|> link_processes([])
end
def link_processes([pid1, pid2 | rest], linked_processes) do
send(pid1, {:link, pid2})
:timer.sleep(1)
{:links, links} = Process.info(pid1, :links)
link_processes(
[pid2 | rest], [%{"pid" => pid1, "links" => links} | linked_processes]
)
end
def link_processes([pid | []], linked_processes) do
%{"pid" => first_pid, "links" => _} = List.last(linked_processes)
send(pid, {:link, first_pid})
:timer.sleep(1)
{:links, links} = Process.info(pid, :links)
[%{"pid" => pid, "links" => links} | linked_processes]
end
#spec create_processes(integer) :: [pid]
def create_processes(num_of_processes) do
for _ <- 1..num_of_processes, do: spawn(__MODULE__, :loop, [])
end
This is because you're linking the processes at the same time as collecting its :links, but some links for that process are being created after you collect its links.
For example, if you spawn a process a, and then collect its links, it'll be an empty list.
iex(1)> a = spawn(fn -> :timer.sleep(:infinity) end)
#PID<0.82.0>
iex(2)> Process.info(a, :links)
{:links, []}
If you spawn b now and link it to a, b will have [a] in its links and a will have [b].
iex(3)> b = spawn(fn -> Process.link(a); :timer.sleep(:infinity) end)
#PID<0.85.0>
iex(4)> Process.info(b, :links)
{:links, [#PID<0.82.0>]}
iex(5)> Process.info(a, :links)
{:links, [#PID<0.85.0>]}
So, you need to collect the links for each process after all the linking is complete if you want the final links for each process.

Test pre-requisites for tabular tests; how does tabular work?

Let's say I am attempting to test an api that is supposed to handle presence or absence of certain object fields.
Let's say I have tests like so:
(def without-foo
{:bar "17"})
(def base-request
{:foo "12"
:bar "17"})
(def without-bar
{:foo "12"})
(def response
{:foo "12"
:bar "17"
:name "Bob"})
(def response-without-bar
{:foo "12"
:bar ""
:name "Bob"})
(def response-without-foo
{:bar "17"
:foo ""
:name "Bob"})
(facts "blah"
(against-background [(external-api-call anything) => {:name => "Bob"})
(fact "base"
(method-under-test base-request) => response)
(fact "without-foo"
(method-under-test without-foo) => response-without-foo)
(fact "without-bar"
(method-under-test without-bar) => response-without-bar))
This works as you would expect and the tests pass. Now I am attempting to refactor this using tabular like so:
(def request
{:foo "12"
:bar "17"})
(def response
{:foo "12"
:bar "17"
:name "Bob"})
(tabular
(against-background [(external-api-call anything) => {:name "Bob"})]
(fact
(method-under-test (merge request ?diff) => (merge response ?rdiff))
?diff ?rdiff ?description
{:foo nil} {:foo ""} "without foo"
{} {} "base case"
{:bar nil} {bar ""} "without bar")
Which results in:
FAIL at (test.clj:123)
Midje could not understand something you wrote:
It looks like the table has no headings, or perhaps you
tried to use a non-literal string for the doc-string?
Ultimately I ended up with:
(tabular
(fact
(method-under-test (merge request ?diff) => (merge response ?rdiff) (provided(external-api-call anything) => {:name "Bob"}))
?diff ?rdiff ?description
{:foo nil} {:foo ""} "without foo"
{} {} "base case"
{:bar nil} {bar ""} "without bar")
Which passes. My question is. How does the tabular function differ from the facts function, and why does one of them accept an against-background while the other blows up?
You need to have following nesting if you want to establish background prerequisites for all your tabular based facts:
(against-background [...]
(tabular
(fact ...)
?... ?...))
For example:
(require '[midje.repl :refer :all])
(defn fn-a []
(throw (RuntimeException. "Not implemented")))
(defn fn-b [k]
(-> (fn-a) (get k)))
(against-background
[(fn-a) => {:a 1 :b 2 :c 3}]
(tabular
(fact
(fn-b ?k) => ?v)
?k ?v
:a 1
:b 3
:c 3))
(check-facts)
;; => All checks (3) succeeded.
If you want to have a background prerequisite per each tabular case you need to nest it as following:
(tabular
(against-background [...]
(fact ...))
?... ?...)
It's important to have the table just under tabular level, not nested in against-background or fact.
For example:
(require '[midje.repl :refer :all])
(defn fn-a []
(throw (RuntimeException. "Not implemented")))
(defn fn-b [k]
(-> (fn-a) (get k)))
(tabular
(against-background
[(fn-a) => {?k ?v}]
(fact
(fn-b ?k) => ?v))
?k ?v
:a 1
:b 2
:c 3)
(check-facts)
;; => All checks (3) succeeded.
In your code it looks like the tabular data is not positioned correctly (parentheses, brackets and curly braces are not balanced correctly so it's impossible to say what exactly is incorrect).

Merge nested params via link_to

I'm using nested params (via ransack nested as q) alongside normal params to build links on a page and am having trouble with getting the two to play nicely when I try and merge the nested params with the other params.
For example if I have:
{"freq"=>"weekly", "loan_amount"=>"350000",
"q"=>{"lowEquity_true"=>"1", "s"=>"rate asc"}}
and try and build a link to change the param "lowEquity_true" with
rates_url(params[:q].merge(:lowEquity_true => '0'))
then I end up with the new params below, which looks like its updated q but dropped the rest of the params.
{"lowEquity_true"=>"0", "s"=>"rate asc"}
If I instead try to merge q & merge into the other params it doesn't update q, and just merges what was in q into the other params instead
rates_url(params[:q].merge(:lowEquity_true => '0').merge(params))
{"freq"=>"weekly", "loan_amount"=>"350000", "lowEquity_true"=>"0",
"q"=>{"lowEquity_true"=>"1", "s"=>"rate asc"},
"s"=>"rate asc"}
I've tried all sorts of various combinations and don't appear to be getting anywhere so am sure that I'm missing something basic!
You are doing it wrong.
Let me explain with an example :
params = {:a => 1, :b => 2, :q => {:x => 24, :y => 25}}
At this point, params[:q] is
{:x=>24, :y=>25}
If I do,
params[:q].merge(:x => 99)
then my params[:q] will become
{:x=>99, :y=>25}
and this is what you are supplying as an argument to rates_url(params[:q].merge(:lowEquity_true => '0'))
that's why only {"lowEquity_true"=>"0", "s"=>"rate asc"} is passed to rates_url as parameters.
Now, if you do something like
params[:q].merge(:x => 99).merge(params)
then params[:q].merge(:x => 99) gives you {:x=>99, :y=>25} and then it merges {:x=>99, :y=>25} into the original params {:a => 1, :b => 2, :q => {:x => 24, :y => 25}}
, so this results into
{:x=>99, :y=>25, :a=>1, :b=>2, :q=>{:x=>24, :y=>25}}
Now, let me explain you what you should do :-
You params is
{"freq"=>"weekly", "loan_amount"=>"350000",
"q"=>{"lowEquity_true"=>"1", "s"=>"rate asc"}}
So, you should do :
params[:q].merge!(:lowEquity_true => '0')
rates_url(params)
That's it
I hope you khow the difference between merge and merge! :-
merge! is destructive, it will modify the original paramter where as merge will not unless you take it in a variable and use it.
Alternatively, if you want to do the same thing stated above in a single line then, just do
rates_url(params.merge!(:q => {:lowEquity_true => '0', "s"=>"rate asc"}))
OR
rates_url(params.merge(:q => params[:q].merge(:lowEquity_true => '0')))

Insert statement not working using execute(array()) of PDO Extension

$stmt = $conn->prepare("INSERT INTO user VALUES ('',:username,md5(:password),'',1,'','',:email,'',0,0,'',:cover,:dateofbirthYear:dateofbirthMonth:dateofbirthDay,NOW(),:sex,:country)");
$stmt->execute(array(
':username' => $username,
':password' => $password,
':email' => $email,
':cover' => $cover,
':dateofbirthYear' => $dateofbirthYear,
':dateofbirthMonth' => $dateofbirthMonth,
':dateofbirthDay' => $dateofbirthDay,
':sex' => $sex,
':country' => $country
));
For some reason this insert statement is not working. I am very new in PDO so I do not know much about it. What am I doing wrong?
this statment gives me this error :
Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens' in /home/manga/public_html/new/register.php:80 Stack trace:
#0 /home/manga/public_html/new/register.php(80): PDOStatement->execute(Array)
#1 {main} thrown in /home/manga/public_html/new/register.php on line 80
You have prepared your query in the wrong way
INSERT INTO user VALUES ('',:username,md5(:password),'',1,'','',:email,'',0,0,'',
:cover,:dateofbirthYear:dateofbirthMonth:dateofbirthDay,NOW(),:sex,:country
// ^ These need to either single or separated
For what you are trying, you can do it this way
//Prepare the date of birth earlier
$dob = $dateofbirthYear.$dateofbirthMonth.$dateofbirthDay;
//Then pass it as a single $variable
$stmt = $conn->prepare("INSERT INTO user VALUES ('',:username,md5(:password),'',1,'','',:email,'',0,0,'',:cover,:dob,NOW(),:sex,:country)");
$stmt->execute(array(
':username' => $username,
':password' => $password,
':email' => $email,
':cover' => $cover,
':dob' => $dob, // <-- Problem solved
':sex' => $sex,
':country' => $country
));
// Then it will execute
The exact error message you have is:
SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens
This means that the number/names of parameters you have passed (the array() in execute) does not match with the number/names of parameters you have in the prepare() SQL-query.
If you compare that with the other questions that contain SQLSTATE[HY093] you will see that it is often related to code that is large and bad formatted which is hard to read. That makes it hard to count. And then you have an oversight of something and then the error happened.
Just fix it and done, for example you can not make one parameter out of three names:
,:dateofbirthYear:dateofbirthMonth:dateofbirthDay,
Instead just pass one parameter for the birthday:
, :dateofbirth,
You can also make your code a bit more readable:
$stmt = $conn->prepare(
"INSERT INTO user
VALUES (
'', :username, md5(:password), '', 1, '', '', :email, '', 0, 0, '',
:cover, :dateofbirth, NOW(), :sex, :country
)"
);
$stmt->execute(array(
':username' => $username,
':password' => $password,
':email' => $email,
':cover' => $cover,
':dateofbirth' => $dateofbirthYear . $dateofbirthMonth . $dateofbirthDay,
':sex' => $sex,
':country' => $country
));
And then you have a security problem with the password hash:
md5(:password)
Instead do proper password hashing, see the PHP FAQ about Safe Password Hashing.
Corrected prepared query:
$stmt = $conn->prepare("INSERT INTO user VALUES ('',:username,md5(:password),'',1,'','',:email,'',0,0,'',:cover,:dateofbirthYear,:dateofbirthMonth:,dateofbirthDay,NOW(),:sex,:country)");
//:dateofbirthYear,:dateofbirthMonth:,dateofbirthDay place holders are seprated
$stmt->execute(array(
':username' => $username,
':password' => $password,
':email' => $email,
':cover' => $cover,
':dateofbirthYear' => $dateofbirthYear,
':dateofbirthMonth' => $dateofbirthMonth,
':dateofbirthDay' => $dateofbirthDay,
':sex' => $sex,
':country' => $country
));

complex search with thinking sphinx

I'd like to do a complex search with thinking sphinx:
Search for users which:
-> live in a city (city_id attribute)
-> or has hability to move to a city (mobile_cities association)
-> or live at a maximum distance from a lat/long point, the maximum distance is different for each user and set in a mobility_distance attribute.
For now I did that with 3 differents search, I volontary set a big per_page number, then i merge the 3 results on a single array, an then paginate this array :
#users living in the #city
search_set_living = search_set.merge({:city_id => #city.id })
users_living = User.search :with => search_set_living.dup,
:page => 1, :per_page => 1000
#users declaring hability to move to the #city
search_set_mobile = search_set.merge({:mobile_cities_ids => #city.id })
users_mobile = User.search :with => search_set_mobile.dup, :page => 1, :per_page => 1000
#users living at a maximum distance from the origin point(custom distance for each user, max 30km)
search_set_around = search_set.merge({"#geodist" => 0.0..30_000.0})
users_around = User.search :geo => [#search_latitude * Math::PI / 180 , #search_longitude * Math::PI / 180],
:with => search_set_around.dup,
:page => 1, :per_page => 1000
users_around_filtered = users_around.dup.delete_if{|user| (user.mobility_distance * 1000 )< user.sphinx_attributes['#geodist'] }
#merge the 3 results in a array
all_users = (users_mobile.flatten + users_around_filtered.flatten).uniq
#look for facets and paginate the array
#facets = User.facets :with => {:user_id => all_users.map(&:id)}
#users_to_display = all_users.paginate(:page => params[:page], :per_page => 10)
This is working fine but i'm not satisfied:
-performance are not so good,
-I want the ability to sort on multiple attributes like this :order => "created_at DESC, #relevance DESC"
I want to do the exact same search but in a single sphinx's search.
I know that I should use the "OR Logic with Attribute Filters" from the docs but I don't know how to mix it with a geo_search call...
I really have no idea how to do that,
can you guys help me ?
Many thanks,
The :sphinx_select option is definitely your friend here, as you've guessed. Let's piece it together bit by bit:
logic = [
"city_id = #{#city.id}",
"IN(mobile_cities_ids, #{#city.id}",
"GEODIST(lat, lng, #{lat}, #{lng}) < (mobility_distance * 1000)"
]
User.search :sphinx_select => "*, #{logic.join(" OR ")}) AS valid",
:with => {:valid => true}
Add pagination as you like, tweak the attribute names if needed (maybe your lat/lng attributes are named something else). I don't think you need the IF call around that custom attribute like in the docs, but if things aren't working when they should be, maybe give it a shot. Should be good in a facets call too.
Great ! Thank you so much. I just needed to correct a little your syntax (some parenthesis missing) in order to get it work.
I had to add per_page and page arguments too, don't know really why.
logic = ["city_id = #{#city.id}",
"IN(mobile_cities_ids, #{#city.id})",
"GEODIST(latitude, longitude, #{#search_latitude * Math::PI / 180}, #{#search_longitude * Math::PI / 180}) < (mobility_distance * 1000)"]
search_set_logic = search_set.merge({:valid => true})
#users_to_display = User.search :sphinx_select => "*, (#{logic.join(" OR ")}) AS valid",
:with => search_set_logic.dup,
:sort_mode => :extended,
:order => "visibility DESC, last_login_at DESC",
:page => params[:page], :per_page => 10