#!/bin/env python3 '''Script to test the Ajax assignment for Fall 2023. This one uses requests and pymysql (cs304dbi). This version also checks the return value and has (by default) terse output. A command-line arg can request more info. Preconditions: 666 is the movie we are rating. I suggest that you begin by deleting all its ratings, just to have a clean slate: mysql -e 'delete * from movie_viewer_rating where tt = 666; update movie set avgrating = null where tt = 666;' 1 and 2 are existing staff members (Scott and Karen Benson) Testing Procedure: * we set UID to 1 * rate the movie 1 star, using the non-ajax route * get the average rating. Should be 1 * rate the movie 3 stars, using the ajax route * get the average rating. Should be 3 * set the UID to 2 * rate it 2 stars * get the average rating. Should be 2.5 * PUT the rating to 4 * get the average rating. Should be 3.5 * DELETE the rating * get the average rating. Should be 3 The main code is pretty readable. Scott Anderson January 2022 ''' import os import sys import json import requests Verbose = False ## In case I ever want to change these test values. MOVIE_TT = '666' '''For the curious reader. The requests module has functions like request.get() and request.post() but there's also a requests.request(), which I found convenient here because I can then funnel all requests through this one function. This function helps manage the *cookies*, which will include our session cookie, so managing it is essential to being able to login and maintain a session. The requests.request() function takes a keyword argument called "cookies". The response object that it returns also has a "cookies" property. All this really does is check the response to see if it sets the "session" cookie. If so, it stores the response cookies (all of them) in a global variable. Any subsequent requests pass along the prior cookies from the global variable by using the keyword argument. This is essentially what browsers do, though I'm not dealing with cookie expiration and such. ''' session_cookies = None # any cookies set in a prior response object def request(method, path, **kwargs): '''This sends a request, optionally with data via a keyword argument. It manages the cookie jar and prints the response code and the response text, assuming all is well. Certain errors are printed. Returns the response or None if something has gone wrong. ''' global session_cookies url = baseurl + path try: print(method, url, kwargs.get('data', '{}')) resp = requests.request(method, url, allow_redirects = False, cookies = session_cookies, **kwargs) if 'session' in dict(resp.cookies): session_cookies = resp.cookies if Verbose: print(resp.status_code) if resp.ok: if resp.status_code == 200 and Verbose: print('response text', resp.text) return resp if resp.status_code == 404: print(f"response code is 404; your app should support {method} for {path}") return None print(f"response is not ok; Please check that your app supports {method} for {path}") return None except requests.ConnectionError as err: print("Failed to connect; is your app running on the port you specified?") sys.exit(1) except Exception as err: print("Failed to post to URL: ",err) return None def is_number(string): if type(string) is type(1.5): return True if type(string) is type('str'): return False try: val = float(string) return True except: return False def check_result(resp, correct): '''checks the result against given correct value.''' if resp is None: print(f'response is None; request did not succeed at all. \t\t FAIL') return try: json_structure = json.loads(resp.text) except Exception as err: print(f'could not parse response text as JSON. Response text was \n{resp.text}\nError was {err}') return if 'avg' not in json_structure: print(f'''returned JSON did not contain an 'avg' key. It was supposed to.\t\t FAIL''') return avg = json_structure.get('avg') # avg = json.loads(resp.text).get('avg') if not is_number(avg): print(f'avg {avg} is not a number. It is a {type(avg)}; it should be a float. \t\t FAIL') return match = 'SUCCESS' if avg == correct else 'FAIL' print(f'average rating is {avg}; should be {correct}: \t\t{match}') if __name__ == '__main__': if len(sys.argv) > 1: arg = sys.argv[1] if arg.isdigit(): port = arg elif arg == '-h': print(f'Usage: {sys.argv[0]} [port] or -v for verbose output', file=sys.stderr) sys.exit() elif arg == '-v': Verbose = True if len(sys.argv) > 2: port = sys.argv[2] else: port = str(os.getuid()) else: print(f'Error: did not understand {arg}', file=sys.strderr) print(f'Usage: {sys.argv} [port] or -v for verbose output', file=sys.stderr) sys.exit() else: port = str(os.getuid()) #baseurl = f'http://cs.wellesley.edu:{port}' baseurl = f'http://localhost:{port}' # set UID 1 resp = request('POST', '/set-UID-ajax/', data = {'uid': 1}) # delete all past ratings resp = request('POST', '/delete-all-ratings/'+MOVIE_TT) # rate it 3 stars resp = request('POST', '/rating/', data = {'tt': MOVIE_TT, 'stars': 3}) check_result(resp, 3.0) resp = request('GET', '/rating/'+MOVIE_TT) check_result(resp, 3.0) # set UID 2 resp = request('POST', '/set-UID-ajax/', data = {'uid': 2}) # rate it with 4 stars resp = request('POST', '/rating/', data = {'tt': MOVIE_TT, 'stars': 4}) check_result(resp, 3.5) resp = request('GET', '/rating/'+MOVIE_TT) check_result(resp, 3.5) ## ================ more REST API routes ================ resp = request('PUT', '/rating/'+MOVIE_TT, data = {'stars': 5}) check_result(resp, 4.0) resp = request('GET', '/rating/'+MOVIE_TT) check_result(resp, 4.0) resp = request('DELETE', '/rating/'+MOVIE_TT) check_result(resp, 3.0) resp = request('GET', '/rating/'+MOVIE_TT) check_result(resp, 3.0)