Archived
1
0
This repository has been archived on 2023-12-16. You can view files and clone it, but cannot push or open issues or pull requests.
luminotes/controller/test/Test_users.py
Dan Helfman 43c6f54e9f Merged revisions 401-446 via svnmerge from
svn+ssh://torsion.org/home/luminotes/repos/luminotes/branches/postgres

................
  r402 | witten | 2007-10-04 00:48:49 -0700 (Thu, 04 Oct 2007) | 3 lines

  Initialized merge tracking via "svnmerge" with revisions "1-401" from
  svn+ssh://torsion.org/home/luminotes/repos/luminotes/trunk
................
  r404 | witten | 2007-10-04 01:17:07 -0700 (Thu, 04 Oct 2007) | 2 lines

  Beginning a conversion from bsddb to postgres.
................
  r405 | witten | 2007-10-04 01:18:58 -0700 (Thu, 04 Oct 2007) | 9 lines

  Merged revisions 402-404 via svnmerge from
  svn+ssh://torsion.org/home/luminotes/repos/luminotes/trunk

  ........
    r403 | witten | 2007-10-04 01:14:45 -0700 (Thu, 04 Oct 2007) | 2 lines

    Yay, no more stupid deprecation warnings from simplejson about the sre module.
  ........
................
  r406 | witten | 2007-10-04 15:34:39 -0700 (Thu, 04 Oct 2007) | 4 lines

   * Switched back to Python 2.4 because many Python modules in Debian are not packaged to work with Python 2.5
   * Began removal of all references to Scheduler, @async, yield, and so on.
   * Converted Database.py to support PostgreSQL and updated its unit tests accordingly.
................
  r407 | witten | 2007-10-04 16:34:01 -0700 (Thu, 04 Oct 2007) | 2 lines

  All unit tests for the new model classes now pass.
................
  r409 | witten | 2007-10-05 00:53:56 -0700 (Fri, 05 Oct 2007) | 2 lines

  Reordering some columns and adding some indices.
................
  r410 | witten | 2007-10-05 16:08:37 -0700 (Fri, 05 Oct 2007) | 4 lines

  Now adding trash notebooks to user_notebook table. Also switching db
  conversion/verification tools back to require Python 2.5, since they still use
  the old Scheduler, which requires 2.5 generator features.
................
  r411 | witten | 2007-10-06 16:26:56 -0700 (Sat, 06 Oct 2007) | 2 lines

  Lots more unit tests passing. Most of the recent work was on controller.Users and related stuff.
................
  r412 | witten | 2007-10-07 01:52:12 -0700 (Sun, 07 Oct 2007) | 2 lines

  controller.Users unit tests now finally pass!
................
  r413 | witten | 2007-10-07 02:14:10 -0700 (Sun, 07 Oct 2007) | 3 lines

  Got controller.Root unit tests passing.
  Moved fake sql_* function shenanigans from Test_users.py to Test_controller.py, for use by other controller unit tests.
................
  r414 | witten | 2007-10-08 23:11:11 -0700 (Mon, 08 Oct 2007) | 2 lines

  All unit tests pass! Fuck yeah!
................
  r415 | witten | 2007-10-08 23:13:07 -0700 (Mon, 08 Oct 2007) | 2 lines

  Removing all references to Scheduler from luminotes.py
................
  r416 | witten | 2007-10-08 23:54:51 -0700 (Mon, 08 Oct 2007) | 3 lines

  Converted deleted_from to deleted_from_id in a few more places.
  Fixed bug in Users.contents().
................
  r417 | witten | 2007-10-09 00:11:59 -0700 (Tue, 09 Oct 2007) | 3 lines

  Typo fix in Note sql method.
  Adding autocommit flag to Database.next_id() method.
................
  r418 | witten | 2007-10-09 00:13:19 -0700 (Tue, 09 Oct 2007) | 2 lines

  Updating unit test for new auto commit flag.
................
  r419 | witten | 2007-10-09 00:14:09 -0700 (Tue, 09 Oct 2007) | 2 lines

  Removing debugging print.
................
  r420 | witten | 2007-10-09 00:20:55 -0700 (Tue, 09 Oct 2007) | 2 lines

  More sql fixes. I really need some funtional tests that hit the database and exercise the SQL.
................
  r421 | witten | 2007-10-09 00:51:34 -0700 (Tue, 09 Oct 2007) | 3 lines

  Fixed controller.Database handling of tuple as an Object_type.
  Made SQL for user storage calculation better at handling null values and also more succinct.
................
  r422 | witten | 2007-10-09 13:32:16 -0700 (Tue, 09 Oct 2007) | 2 lines

  Converting Wiki.js to trash_id notebook member instead of trash object.
................
  r423 | witten | 2007-10-09 13:42:10 -0700 (Tue, 09 Oct 2007) | 2 lines

  No longer displaying "download as html" on the front page, as people see "download" and think they're downloading the software.
................
  r424 | witten | 2007-10-09 14:24:40 -0700 (Tue, 09 Oct 2007) | 2 lines

  Notebooks.contents() now returns notebooks with correct read-write status.
................
  r425 | witten | 2007-10-09 14:32:25 -0700 (Tue, 09 Oct 2007) | 2 lines

  Fixed reporting of validation errors to the user. Now says "The blah is missing." instead of just "is missing"
................
  r426 | witten | 2007-10-09 17:05:22 -0700 (Tue, 09 Oct 2007) | 2 lines

  No longer redirecting to trash notebook upon login.
................
  r427 | witten | 2007-10-09 17:20:33 -0700 (Tue, 09 Oct 2007) | 2 lines

  Made controller.Database use a connection pool.
................
  r429 | witten | 2007-10-09 20:13:30 -0700 (Tue, 09 Oct 2007) | 2 lines

  Converted initdb.py and updatedb.py to Postgres from bsddb.
................
  r430 | witten | 2007-10-09 20:37:14 -0700 (Tue, 09 Oct 2007) | 2 lines

  Changing error message to remove underscores from variable names.
................
  r431 | witten | 2007-10-10 13:23:30 -0700 (Wed, 10 Oct 2007) | 2 lines

  Removing unused note_title parameter from Wiki.create_editor().
................
  r432 | witten | 2007-10-10 13:25:16 -0700 (Wed, 10 Oct 2007) | 2 lines

  Revision regular expression now supports timezone notation.
................
  r433 | witten | 2007-10-10 14:43:47 -0700 (Wed, 10 Oct 2007) | 2 lines

  Finished implementing ranked ordering for startup notes. (However, there's no way to change the rank from the client yet.)
................
  r434 | witten | 2007-10-10 16:25:19 -0700 (Wed, 10 Oct 2007) | 4 lines

  More strict access checking. Fixed oversight in Postgres DB conversion where,
  in certain controller.Notebook methods, access was only checked at the
  notebook level, not at the note level as well.
................
  r435 | witten | 2007-10-10 17:45:18 -0700 (Wed, 10 Oct 2007) | 3 lines

  Now loading revisions on demand from client when the "changes" button is clicked. Also caching
  loading revisions so subsequent clicks don't have to reload.
................
  r436 | witten | 2007-10-10 21:31:20 -0700 (Wed, 10 Oct 2007) | 2 lines

  Tweaking some of the error handling in Expose and Root so that unhandled errors give a generic error message to the client.
................
  r437 | witten | 2007-10-10 21:33:49 -0700 (Wed, 10 Oct 2007) | 2 lines

  The release script no longer runs initdb.py, because the default database is no longer a single file included in the tarball.
................
  r438 | witten | 2007-10-10 21:40:11 -0700 (Wed, 10 Oct 2007) | 2 lines

  Updated install instructuctions to include use of initdb.py.
................
  r439 | witten | 2007-10-10 21:56:42 -0700 (Wed, 10 Oct 2007) | 3 lines

  Made initdb.py only nuke (drop tables/views) when given a command-line flag.
  Also made install directions more correct.
................
  r440 | witten | 2007-10-10 21:58:48 -0700 (Wed, 10 Oct 2007) | 2 lines

  IE 6 doesn't like commas.
................
  r441 | witten | 2007-10-10 22:08:50 -0700 (Wed, 10 Oct 2007) | 4 lines

  load your notebook. without clicking on "changes", edit a note that has previous revisions. click on "changes". it'll only show
  the most recent revision. fixed by not appending to changes as a result of a save unless the client-side revisions list cache has
  something in it
................
  r442 | witten | 2007-10-10 23:30:41 -0700 (Wed, 10 Oct 2007) | 2 lines

  Forgot to actually save off the new revision as editor.revision.
................
  r443 | witten | 2007-10-11 01:35:54 -0700 (Thu, 11 Oct 2007) | 13 lines

  More intelligent datetime handling:
    * convertdb.py assumes old bsddb database timestamps are Pacific, and then
      converts them to UTC before inserting them into the new PostgreSQL
      database.
    * No longer using naked timezoneless datetime objects in model/controller
      code, except in unit tests that need compatability with pysqlite. Now
      using UTC everwhere.
    * Asking PostgreSQL to give us all timestamps back in UTC.
    * New dependency on python-tz (pytz) package, noted in INSTALL doc.
    * Client now responsible for converting UTC timestamps to local time for
      display.
................
  r444 | witten | 2007-10-11 01:46:09 -0700 (Thu, 11 Oct 2007) | 2 lines

  Tweak to prevent potential race in IE.
................
  r445 | witten | 2007-10-11 01:49:58 -0700 (Thu, 11 Oct 2007) | 2 lines

  Got JavaScript "unit" tests passing again.
................
  r446 | witten | 2007-10-11 01:53:58 -0700 (Thu, 11 Oct 2007) | 2 lines

  Noting that js tests require the Luminotes server on localhost.
................
2007-10-11 09:03:43 +00:00

697 lines
25 KiB
Python

import re
import cherrypy
import smtplib
from pytz import utc
from datetime import datetime, timedelta
from nose.tools import raises
from Test_controller import Test_controller
from Stub_smtp import Stub_smtp
from new_model.User import User
from new_model.Notebook import Notebook
from new_model.Note import Note
from new_model.Password_reset import Password_reset
class Test_users( Test_controller ):
RESET_LINK_PATTERN = re.compile( "(https?://\S+)?/(\S+)" )
def setUp( self ):
Test_controller.setUp( self )
self.username = u"mulder"
self.password = u"trustno1"
self.email_address = u"outthere@example.com"
self.new_username = u"reynolds"
self.new_password = u"shiny"
self.new_email_address = u"capn@example.com"
self.username2 = u"scully"
self.password2 = u"trustsome1"
self.email_address2 = u"outthere@example.com"
self.user = None
self.user2 = None
self.anonymous = None
self.notebooks = None
self.make_users()
def make_users( self ):
notebook_id1 = self.database.next_id( Notebook )
notebook_id2 = self.database.next_id( Notebook )
trash_id1 = self.database.next_id( Notebook )
trash_id2 = self.database.next_id( Notebook )
self.notebooks = [
Notebook.create( notebook_id1, u"my notebook", trash_id = trash_id1 ),
Notebook.create( notebook_id2, u"my other notebook", trash_id = trash_id2 ),
]
self.database.save( self.notebooks[ 0 ] )
self.database.save( self.notebooks[ 1 ] )
self.anon_notebook = Notebook.create( self.database.next_id( Notebook ), u"anon notebook" )
self.database.save( self.anon_notebook )
self.startup_note = Note.create(
self.database.next_id( Note ), u"<h3>login</h3>",
notebook_id = self.anon_notebook.object_id, startup = True,
)
self.database.save( self.startup_note )
self.user = User.create( self.database.next_id( User ), self.username, self.password, self.email_address )
self.database.save( self.user, commit = False )
self.database.execute( self.user.sql_save_notebook( notebook_id1, read_write = True ), commit = False )
self.database.execute( self.user.sql_save_notebook( notebook_id2, read_write = True ), commit = False )
self.user2 = User.create( self.database.next_id( User ), self.username2, self.password2, self.email_address2 )
self.database.save( self.user2, commit = False )
self.anonymous = User.create( self.database.next_id( User ), u"anonymous" )
self.database.save( self.anonymous, commit = False )
self.database.execute( self.anonymous.sql_save_notebook( self.anon_notebook.object_id ), commit = False )
self.database.commit()
def test_signup( self ):
result = self.http_post( "/users/signup", dict(
username = self.new_username,
password = self.new_password,
password_repeat = self.new_password,
email_address = self.new_email_address,
signup_button = u"sign up",
) )
assert result[ u"redirect" ].startswith( u"/notebooks/" )
def test_current_after_signup( self, include_startup_notes = False ):
result = self.http_post( "/users/signup", dict(
username = self.new_username,
password = self.new_password,
password_repeat = self.new_password,
email_address = self.new_email_address,
signup_button = u"sign up",
) )
session_id = result[ u"session_id" ]
new_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ]
result = self.http_get(
"/users/current?include_startup_notes=%s" % include_startup_notes,
session_id = session_id,
)
assert result[ u"user" ].username == self.new_username
notebooks = result[ u"notebooks" ]
notebook = notebooks[ 0 ]
assert notebook.object_id == self.anon_notebook.object_id
assert notebook.revision == self.anon_notebook.revision
assert notebook.name == self.anon_notebook.name
assert notebook.trash_id == None
assert notebook.read_write == False
notebook = notebooks[ 1 ]
assert notebook.object_id == new_notebook_id
assert notebook.revision
assert notebook.name == u"my notebook"
assert notebook.trash_id
assert notebook.read_write == True
notebook = notebooks[ 2 ]
assert notebook.object_id == notebooks[ 1 ].trash_id
assert notebook.revision
assert notebook.name == u"trash"
assert notebook.trash_id == None
assert notebook.read_write == True
startup_notes = result[ "startup_notes" ]
if include_startup_notes:
assert len( startup_notes ) == 1
assert startup_notes[ 0 ].object_id == self.startup_note.object_id
assert startup_notes[ 0 ].title == self.startup_note.title
assert startup_notes[ 0 ].contents == self.startup_note.contents
else:
assert startup_notes == []
rate_plan = result[ u"rate_plan" ]
assert rate_plan[ u"name" ] == u"super"
assert rate_plan[ u"storage_quota_bytes" ] == 1337
def test_current_with_startup_notes_after_signup( self ):
self.test_current_after_signup( include_startup_notes = True )
def test_signup_with_different_passwords( self ):
result = self.http_post( "/users/signup", dict(
username = self.new_username,
password = self.new_password,
password_repeat = self.new_password + u"nomatch",
email_address = self.new_email_address,
signup_button = u"sign up",
) )
assert result[ u"error" ]
def test_demo( self ):
result = self.http_post( "/users/demo", dict() )
assert result[ u"redirect" ].startswith( u"/notebooks/" )
def test_current_after_demo( self, include_startup_notes = False ):
result = self.http_post( "/users/demo", dict() )
session_id = result[ u"session_id" ]
new_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ]
result = self.http_get(
"/users/current?include_startup_notes=%s" % include_startup_notes,
session_id = session_id,
)
assert result[ u"user" ].username == None
notebooks = result[ u"notebooks" ]
assert len( notebooks ) == 3
notebook = notebooks[ 0 ]
assert notebook.object_id == self.anon_notebook.object_id
assert notebook.revision == self.anon_notebook.revision
assert notebook.name == self.anon_notebook.name
assert notebook.trash_id == None
assert notebook.read_write == False
notebook = notebooks[ 1 ]
assert notebook.object_id == new_notebook_id
assert notebook.revision
assert notebook.name == u"my notebook"
assert notebook.trash_id
assert notebook.read_write == True
notebook = notebooks[ 2 ]
assert notebook.object_id == notebooks[ 1 ].trash_id
assert notebook.revision
assert notebook.name == u"trash"
assert notebook.trash_id == None
assert notebook.read_write == True
startup_notes = result[ "startup_notes" ]
if include_startup_notes:
assert len( startup_notes ) == 1
assert startup_notes[ 0 ].object_id == self.startup_note.object_id
assert startup_notes[ 0 ].title == self.startup_note.title
assert startup_notes[ 0 ].contents == self.startup_note.contents
else:
assert startup_notes == []
rate_plan = result[ u"rate_plan" ]
assert rate_plan[ u"name" ] == u"super"
assert rate_plan[ u"storage_quota_bytes" ] == 1337
def test_current_with_startup_notes_after_demo( self ):
self.test_current_after_demo( include_startup_notes = True )
def test_current_after_demo_twice( self, include_startup_notes = False ):
result = self.http_post( "/users/demo", dict() )
session_id = result[ u"session_id" ]
new_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ]
result = self.http_get(
"/users/current?include_startup_notes=%s" % include_startup_notes,
session_id = session_id,
)
user_id = result[ u"user" ].object_id
# request a demo for a second time
result = self.http_post( "/users/demo", dict(), session_id = session_id )
assert result[ u"redirect" ].startswith( u"/notebooks/" )
notebook_id_again = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ]
assert notebook_id_again == new_notebook_id
result = self.http_get(
"/users/current?include_startup_notes=%s" % include_startup_notes,
session_id = session_id,
)
user_id_again = result[ u"user" ].object_id
# since we're already logged in as a guest user with a demo notebook, requesting a demo again
# should just use the same guest user with the same notebook
assert user_id_again == user_id
def test_current_with_startup_notes_after_demo_twice( self ):
self.test_current_after_demo_twice( include_startup_notes = True )
def test_login( self ):
result = self.http_post( "/users/login", dict(
username = self.username,
password = self.password,
login_button = u"login",
) )
assert result[ u"redirect" ] == u"/notebooks/%s" % self.notebooks[ 0 ].object_id
def test_login_with_unknown_user( self ):
result = self.http_post( "/users/login", dict(
username = u"nosuchuser",
password = self.password,
login_button = u"login",
) )
assert result[ u"error" ]
def test_login_with_invalid_password( self ):
result = self.http_post( "/users/login", dict(
username = self.username,
password = u"wrongpass",
login_button = u"login",
) )
assert result[ u"error" ]
def test_logout( self ):
result = self.http_post( "/users/logout", dict() )
assert result[ u"redirect" ] == self.settings[ u"global" ].get( u"luminotes.http_url" ) + u"/"
def test_current_after_login( self, include_startup_notes = False ):
result = self.http_post( "/users/login", dict(
username = self.username,
password = self.password,
login_button = u"login",
) )
session_id = result[ u"session_id" ]
result = self.http_get(
"/users/current?include_startup_notes=%s" % include_startup_notes,
session_id = session_id,
)
assert result[ u"user" ]
assert result[ u"user" ].object_id == self.user.object_id
assert result[ u"user" ].username == self.user.username
assert len( result[ u"notebooks" ] ) == 3
assert result[ u"notebooks" ][ 0 ].object_id == self.anon_notebook.object_id
assert result[ u"notebooks" ][ 0 ].read_write == False
assert result[ u"notebooks" ][ 1 ].object_id == self.notebooks[ 0 ].object_id
assert result[ u"notebooks" ][ 1 ].read_write == True
assert result[ u"notebooks" ][ 2 ].object_id == self.notebooks[ 1 ].object_id
assert result[ u"notebooks" ][ 2 ].read_write == True
assert result[ u"http_url" ] == self.settings[ u"global" ].get( u"luminotes.http_url" )
assert result[ u"login_url" ] == None
startup_notes = result[ "startup_notes" ]
if include_startup_notes:
assert len( startup_notes ) == 1
assert startup_notes[ 0 ].object_id == self.startup_note.object_id
assert startup_notes[ 0 ].title == self.startup_note.title
assert startup_notes[ 0 ].contents == self.startup_note.contents
else:
assert startup_notes == []
def test_current_with_startup_notes_after_login( self ):
self.test_current_after_login( include_startup_notes = True )
def test_current_without_login( self, include_startup_notes = False ):
result = self.http_get(
"/users/current?include_startup_notes=%s" % include_startup_notes,
)
assert result[ u"user" ].username == "anonymous"
assert len( result[ u"notebooks" ] ) == 1
assert result[ u"notebooks" ][ 0 ].object_id == self.anon_notebook.object_id
assert result[ u"notebooks" ][ 0 ].name == self.anon_notebook.name
assert result[ u"notebooks" ][ 0 ].read_write == False
assert result[ u"http_url" ] == self.settings[ u"global" ].get( u"luminotes.http_url" )
login_note = self.database.select_one( Note, self.anon_notebook.sql_load_note_by_title( u"login" ) )
assert result[ u"login_url" ] == u"%s/notebooks/%s?note_id=%s" % (
self.settings[ u"global" ][ u"luminotes.https_url" ],
self.anon_notebook.object_id,
login_note.object_id,
)
startup_notes = result[ "startup_notes" ]
if include_startup_notes:
assert len( startup_notes ) == 1
assert startup_notes[ 0 ].object_id == self.startup_note.object_id
assert startup_notes[ 0 ].title == self.startup_note.title
assert startup_notes[ 0 ].contents == self.startup_note.contents
else:
assert startup_notes == []
def test_current_with_startup_notes_without_login( self ):
self.test_current_without_login( include_startup_notes = True )
def test_update_storage( self ):
previous_revision = self.user.revision
cherrypy.root.users.update_storage( self.user.object_id )
expected_size = cherrypy.root.users.calculate_storage( self.user )
user = self.database.load( User, self.user.object_id )
assert user.storage_bytes == expected_size
assert user.revision > previous_revision
def test_update_storage_with_unknown_user_id( self ):
original_revision = self.user.revision
cherrypy.root.users.update_storage( 77 )
expected_size = cherrypy.root.users.calculate_storage( self.user )
user = self.database.load( User, self.user.object_id )
assert self.user.storage_bytes == 0
assert self.user.revision == original_revision
def test_send_reset( self ):
# trick send_reset() into using a fake SMTP server
Stub_smtp.reset()
smtplib.SMTP = Stub_smtp
result = self.http_post( "/users/send_reset", dict(
email_address = self.user.email_address,
send_reset_button = u"email me",
) )
session_id = result[ u"session_id" ]
assert u"has been sent to" in result[ u"message" ]
assert smtplib.SMTP.connected == False
assert "<%s>" % self.settings[ u"global" ][ u"luminotes.support_email" ] in smtplib.SMTP.from_address
assert smtplib.SMTP.to_addresses == [ self.user.email_address ]
assert u"password reset" in smtplib.SMTP.message
assert self.RESET_LINK_PATTERN.search( smtplib.SMTP.message )
def test_send_reset_to_unknown_email_address( self ):
Stub_smtp.reset()
smtplib.SMTP = Stub_smtp
result = self.http_post( "/users/send_reset", dict(
email_address = u"unknown@example.com",
send_reset_button = u"email me",
) )
assert u"no Luminotes user" in result[ u"error" ]
assert smtplib.SMTP.connected == False
assert smtplib.SMTP.from_address == None
assert smtplib.SMTP.to_addresses == None
assert smtplib.SMTP.message == None
def test_redeem_reset( self ):
Stub_smtp.reset()
smtplib.SMTP = Stub_smtp
self.http_post( "/users/send_reset", dict(
email_address = self.user.email_address,
send_reset_button = u"email me",
) )
matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message )
password_reset_id = matches.group( 2 )
assert password_reset_id
result = self.http_get( "/users/redeem_reset/%s" % password_reset_id )
assert result[ u"notebook_id" ] == self.anon_notebook.object_id
assert result[ u"note_id" ]
assert u"password reset" in result[ u"note_contents" ]
assert self.user.username in result[ u"note_contents" ]
assert self.user2.username in result[ u"note_contents" ]
def test_redeem_reset_unknown( self ):
password_reset_id = u"unknownresetid"
result = self.http_get( "/users/redeem_reset/%s" % password_reset_id )
assert u"expired" in result[ u"error" ]
def test_redeem_reset_expired( self ):
Stub_smtp.reset()
smtplib.SMTP = Stub_smtp
self.http_post( "/users/send_reset", dict(
email_address = self.user.email_address,
send_reset_button = u"email me",
) )
matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message )
password_reset_id = matches.group( 2 )
assert password_reset_id
# to trigger expiration, pretend that the password reset was made 25 hours ago
password_reset = self.database.load( Password_reset, password_reset_id )
password_reset._Persistent__revision = datetime.now( tz = utc ) - timedelta( hours = 25 )
self.database.save( password_reset )
result = self.http_get( "/users/redeem_reset/%s" % password_reset_id )
assert u"expired" in result[ u"error" ]
def test_redeem_reset_already_redeemed( self ):
Stub_smtp.reset()
smtplib.SMTP = Stub_smtp
self.http_post( "/users/send_reset", dict(
email_address = self.user.email_address,
send_reset_button = u"email me",
) )
matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message )
password_reset_id = matches.group( 2 )
assert password_reset_id
password_reset = self.database.load( Password_reset, password_reset_id )
password_reset.redeemed = True
self.database.save( password_reset )
result = self.http_get( "/users/redeem_reset/%s" % password_reset_id )
assert u"already" in result[ u"error" ]
def test_redeem_reset_unknown_email( self ):
Stub_smtp.reset()
smtplib.SMTP = Stub_smtp
self.http_post( "/users/send_reset", dict(
email_address = self.user.email_address,
send_reset_button = u"email me",
) )
matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message )
password_reset_id = matches.group( 2 )
assert password_reset_id
password_reset = self.database.load( Password_reset, password_reset_id )
password_reset._Password_reset__email_address = u"unknown@example.com"
self.database.save( password_reset )
result = self.http_get( "/users/redeem_reset/%s" % password_reset_id )
assert u"email address" in result[ u"error" ]
def test_reset_password( self ):
Stub_smtp.reset()
smtplib.SMTP = Stub_smtp
self.http_post( "/users/send_reset", dict(
email_address = self.user.email_address,
send_reset_button = u"email me",
) )
matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message )
password_reset_id = matches.group( 2 )
assert password_reset_id
new_password = u"newpass"
result = self.http_post( "/users/reset_password", (
( u"password_reset_id", password_reset_id ),
( u"reset_button", u"reset passwords" ),
( self.user.object_id, new_password ),
( self.user.object_id, new_password ),
( self.user2.object_id, u"" ),
( self.user2.object_id, u"" ),
) )
assert result[ u"redirect" ]
# check that the password reset is now marked as redeemed
password_reset = self.database.load( Password_reset, password_reset_id )
assert password_reset.redeemed
# check that the password was actually reset for one of the users, but not the other
user = self.database.load( User, self.user.object_id )
assert user.check_password( new_password )
user2 = self.database.load( User, self.user2.object_id )
assert user2.check_password( self.password2 )
def test_reset_password_unknown_reset_id( self ):
new_password = u"newpass"
password_reset_id = u"unknownresetid"
result = self.http_post( "/users/reset_password", (
( u"password_reset_id", password_reset_id ),
( u"reset_button", u"reset passwords" ),
( self.user.object_id, new_password ),
( self.user.object_id, new_password ),
( self.user2.object_id, u"" ),
( self.user2.object_id, u"" ),
) )
assert u"expired" in result[ "error" ]
# check that neither user's password has changed
user = self.database.load( User, self.user.object_id )
assert user.check_password( self.password )
user2 = self.database.load( User, self.user2.object_id )
assert user2.check_password( self.password2 )
def test_reset_password_invalid_reset_id( self ):
new_password = u"newpass"
password_reset_id = u"invalid reset id"
result = self.http_post( "/users/reset_password", (
( u"password_reset_id", password_reset_id ),
( u"reset_button", u"reset passwords" ),
( self.user.object_id, new_password ),
( self.user.object_id, new_password ),
( self.user2.object_id, u"" ),
( self.user2.object_id, u"" ),
) )
assert u"valid" in result[ "error" ]
# check that neither user's password has changed
user = self.database.load( User, self.user.object_id )
assert user.check_password( self.password )
user2 = self.database.load( User, self.user2.object_id )
assert user2.check_password( self.password2 )
def test_reset_password_expired( self ):
Stub_smtp.reset()
smtplib.SMTP = Stub_smtp
self.http_post( "/users/send_reset", dict(
email_address = self.user.email_address,
send_reset_button = u"email me",
) )
matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message )
password_reset_id = matches.group( 2 )
assert password_reset_id
# to trigger expiration, pretend that the password reset was made 25 hours ago
password_reset = self.database.load( Password_reset, password_reset_id )
password_reset._Persistent__revision = datetime.now( tz = utc ) - timedelta( hours = 25 )
self.database.save( password_reset )
new_password = u"newpass"
result = self.http_post( "/users/reset_password", (
( u"password_reset_id", password_reset_id ),
( u"reset_button", u"reset passwords" ),
( self.user.object_id, new_password ),
( self.user.object_id, new_password ),
( self.user2.object_id, u"" ),
( self.user2.object_id, u"" ),
) )
# check that the password reset is not marked as redeemed
password_reset = self.database.load( Password_reset, password_reset_id )
assert password_reset.redeemed == False
assert u"expired" in result[ "error" ]
# check that neither user's password has changed
user = self.database.load( User, self.user.object_id )
assert user.check_password( self.password )
user2 = self.database.load( User, self.user2.object_id )
assert user2.check_password( self.password2 )
def test_reset_password_non_matching( self ):
Stub_smtp.reset()
smtplib.SMTP = Stub_smtp
self.http_post( "/users/send_reset", dict(
email_address = self.user.email_address,
send_reset_button = u"email me",
) )
matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message )
password_reset_id = matches.group( 2 )
assert password_reset_id
new_password = u"newpass"
result = self.http_post( "/users/reset_password", (
( u"password_reset_id", password_reset_id ),
( u"reset_button", u"reset passwords" ),
( self.user.object_id, new_password ),
( self.user.object_id, u"nonmatchingpass" ),
( self.user2.object_id, u"" ),
( self.user2.object_id, u"" ),
) )
assert u"password" in result[ "error" ]
# check that neither user's password has changed
user = self.database.load( User, self.user.object_id )
assert user.check_password( self.password )
user2 = self.database.load( User, self.user2.object_id )
assert user2.check_password( self.password2 )
def test_reset_password_blank( self ):
Stub_smtp.reset()
smtplib.SMTP = Stub_smtp
self.http_post( "/users/send_reset", dict(
email_address = self.user.email_address,
send_reset_button = u"email me",
) )
matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message )
password_reset_id = matches.group( 2 )
assert password_reset_id
result = self.http_post( "/users/reset_password", (
( u"password_reset_id", password_reset_id ),
( u"reset_button", u"reset passwords" ),
( self.user.object_id, u"" ),
( self.user.object_id, u"" ),
( self.user2.object_id, u"" ),
( self.user2.object_id, u"" ),
) )
assert result[ "error" ]
# check that neither user's password has changed
assert self.user.check_password( self.password )
assert self.user2.check_password( self.password2 )
def test_reset_password_multiple_users( self ):
Stub_smtp.reset()
smtplib.SMTP = Stub_smtp
self.http_post( "/users/send_reset", dict(
email_address = self.user.email_address,
send_reset_button = u"email me",
) )
matches = self.RESET_LINK_PATTERN.search( smtplib.SMTP.message )
password_reset_id = matches.group( 2 )
assert password_reset_id
new_password = u"newpass"
new_password2 = u"newpass2"
result = self.http_post( "/users/reset_password", (
( u"password_reset_id", password_reset_id ),
( u"reset_button", u"reset passwords" ),
( self.user.object_id, new_password ),
( self.user.object_id, new_password ),
( self.user2.object_id, new_password2 ),
( self.user2.object_id, new_password2 ),
) )
assert result[ u"redirect" ]
# check that the password reset is now marked as redeemed
password_reset = self.database.load( Password_reset, password_reset_id )
assert password_reset.redeemed
# check that the password was actually reset for both users
user = self.database.load( User, self.user.object_id )
assert user.check_password( new_password )
user2 = self.database.load( User, self.user2.object_id )
assert user2.check_password( new_password2 )