Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Mocking LDAP in your tests

by Ea (Hermit)
on May 31, 2018 at 14:48 UTC ( #1215545=perlmeditation: print w/replies, xml ) Need Help??

Your mother was X.500 and your father smells of RFCs! Now go away or I shall mock you a second time!
  - from the original draft of Monty Python and the Holy Grail.

I finally buckled down and starting to mock the LDAP server in my tests rather than trying to connect to a live server. Other than the documentation, there's not a lot of examples out there for Test::Net::LDAP::Mock or Test::Net::LDAP::Util, so here's the results from banging away at it for an afternoon along with what I think is going on. Please feel free to point out what I've done wrong. I've stopped where it started working for me.

I have a Mojolcious app that authenticates against LDAP, but the tests would fail when using dummy accounts or when I wasn't connected. Here's the test I wrote

use Mojo::Base -strict; use Test::More; use Test::Mojo; use Test::Net::LDAP::Util qw/ldap_mockify/; my $t = Test::Mojo->new('MyApp'); my $basedn = 'dc=ldap,dc=perl,dc=org'; ldap_mockify { # mock the LDAP server and add entries my $ldap = Net::LDAP->new('ldap.example.com'); $ldap->add("uid=good,$basedn", attrs => [ userid => 'whitecamel']) +; $ldap->add("uid=evil,$basedn", attrs => [ userid => 'blackperl']); $ldap->mock_bind(sub { my $arg = shift; if ($arg->{dn}->dn() eq "uid=good,$basedn" && $arg->{password} eq 'LetMeIn' ) { return Net::LDAP::Constant::LDAP_SUCCESS; } else { return Net::LDAP::Constant::LDAP_INVALID_CREDENTIALS; } } ); # test the app $t->post_ok('/login' => {Accept => '*/*'} => form => {username => 'rubyred', password => 'nosuchuser'} ) ->status_is(403, 'User not found in LDAP'); $t->post_ok('/login' => {Accept => '*/*'} => form => {username => 'blackperl', password => 'Arrrgh'}) ->status_is(403, 'No access for incorrect password'); $t->post_ok('/login' => {Accept => '*/*'} => form => {username => 'whitecamel', password => 'wrongpassword'}) ->status_is(403, 'No access for incorrect password'); $t->get_ok('/secure/protected') ->status_is(401, 'Protected page is inaccessible without correct login'); $t->post_ok('/login' => {Accept => '*/*'} => form => {username => 'whitecamel', password => 'LetMeIn'} ) ->status_is(302, 'Redirected to Welcome page on successful login') ->content_like(qr/Welcome/); $t->get_ok('/secure/protected') ->status_is(200) ->content_like(qr/This is a protected page/, 'Protected page now accessible'); }; done_testing();

Steps to mocking

Setup your test environment as usual and use Test::Net::LDAP::Util qw/ldap_mockify/; The ldap_mockify method intercepts all calls to Net::LDAP->new() and redirects them to your mocked LDAP directory.
  1. Create a new Net::LDAP object
  2. Use the object to populate your mocked server with data using the add method
  3. If you want to mock the authentication process, use the mock_bind method with a call back that returns LDAP_SUCCESS or LDAP_INVALID_CREDENTIALS
  4. Now that your LDAP server is all mocked up, run your tests
  5. Don't forget the }; at the end of the method. It's a funny error message when you forget the semicolon at the end.

Notes

  • the $basedn that you mock has to be the same as the base DN that you search in your application. this is easier if you keep the values in a config file and read the same file in your test (not shown here for brevity)
  • testing authentication, you don't set the password for an entry with mock_password(), but instead supply mock_bind() with a callback
  • if you haven't imported Net::LDAP::Constant, you'll need to use the fully qualified name to report success Net::LDAP::Constant::LDAP_SUCCESS or failure Net::LDAP::Constant::LDAP_INVALID_CREDENTIALS
  • most of the methods in Test::Net::LDAP::Util seem to want to return success, regardless of the underlying data, which can be frustrating until you work that out and code accordingly.

Well, what do you think? Does it get the job done?

Edit - while cleaning up tabs used for putting this post together, I found a relevant question on StackOverflow from 5 years ago, but it hasn't been answered so far.

Ea

Sometimes I can think of 6 impossible LDAP attributes before breakfast.

YAPC::Europe::2018 — Hmmm, need to talk to work about sending me there or to Mojoconf.

Replies are listed 'Best First'.
Re: Mocking LDAP in your tests
by shmem (Chancellor) on Jun 01, 2018 at 00:13 UTC

    Interesting. Maybe my approach to that is too lousy:

    Since Net::LDAP supports searching a LDIF file, I do mocking by producing a reasonable, condensed LDIF file and using that instead of an LDAP server for searches. I wrap the search() method into one which rewinds the LDIF filehandle for subsequent searches.

    Now, because bind() is a combined procedure of hashing a password and a search, it is trivial to set up a mock_bind() function which does just that, and produces the appropriate result.

    Yes, searching is slow, if the LDIF file is big. What do you think? What am I missing?

    perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
      My understanding is that your application should not contain code exists only there to make the tests pass. If you do all the work in your test, then it doesn't matter if you're working offline, your tests should run without complaining that they can't connect to the server. Keeping an LDIF file in your tests makes a lot of sense in the separation of code. To counter problems with slow tests, I suppose you want the setup/teardown to be efficient, the LDIF only as big as necessary and to consider Continuous Integration to run the full test suite automatically.

      Is your LDAP sitting in your application or is it external? Would you be able to post some test code snippits? I'd like to see how you do it.

      Ea

      Sometimes I can think of 6 impossible LDAP attributes before breakfast.

      YAPC::Europe::2018 — Hmmm, need to talk to work about sending me there or to Mojoconf.

      Sorry to barge in, but how exactly do you use Net::LDAP to search through LDIF files? Did you create a custom search for that?

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://1215545]
Approved by marto
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others about the Monastery: (10)
As of 2018-09-24 17:54 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Eventually, "covfefe" will come to mean:













    Results (192 votes). Check out past polls.

    Notices?
    • (Sep 10, 2018 at 22:53 UTC) Welcome new users!