1 ;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: HUNCHENTOOT; Base: 10 -*-
3 ;;; Copyright (c) 2004-2010, Dr. Edmund Weitz. All rights reserved.
5 ;;; Redistribution and use in source and binary forms, with or without
6 ;;; modification, are permitted provided that the following conditions
9 ;;; * Redistributions of source code must retain the above copyright
10 ;;; notice, this list of conditions and the following disclaimer.
12 ;;; * Redistributions in binary form must reproduce the above
13 ;;; copyright notice, this list of conditions and the following
14 ;;; disclaimer in the documentation and/or other materials
15 ;;; provided with the distribution.
17 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
18 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
21 ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23 ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25 ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 (in-package :hunchentoot-test
)
31 (defun file-contents (pathname &key
(element-type '(unsigned-byte 8)))
32 (with-open-file (s pathname
:element-type element-type
)
33 (let ((result (make-array (file-length s
) :element-type element-type
)))
34 (read-sequence result s
)
37 (defclass script-context
()
38 ((base-url :initarg
:base-url
39 :reader script-context-base-url
40 :documentation
"Base URL to be used for all HTTP requests in this script context")))
42 (defmethod initialize-instance :before
((script-context script-context
) &key context-class-name
)
43 ;; just ignore the context-class-name so that we can use &args in the WITH-SCRIPT-CONTEXT macro below.
44 (declare (ignore context-class-name
)))
46 (defvar *script-context
* nil
47 "Current script context")
49 (defmacro with-script-context
((&rest args
&key
(context-class-name 'script-context
) &allow-other-keys
)
51 `(let ((*script-context
* (make-instance ',context-class-name
,@args
))
52 (*default-pathname-defaults
* *this-file
*)
55 ((assertion-failed (lambda (condition)
56 (push condition failed
)
57 (format t
"Assertion failed:~%~A~%" condition
))))
62 (format t
";; ~A assertion~:P FAILED~%" (length failed
))
63 (format t
";; all tests PASSED~%"))))))
65 (defclass http-reply
()
66 ((body :initarg
:body
)
67 (status-code :initarg
:status-code
)
68 (headers :initarg
:headers
)
70 (stream :initarg
:stream
)
71 (close :initarg
:close
)
72 (reason-phrase :initarg
:reason-phrase
)))
74 (defvar *last-reply
* nil
75 "Contains the last HTTP reply received")
77 (define-condition assertion-failed
(simple-condition)
78 ((assertion :initarg
:assertion
79 :accessor condition-assertion
81 (reply-slot-name :initarg
:reply-slot-name
82 :reader condition-reply-slot-name
)
83 (reply-value :initarg
:reply-value
84 :reader condition-reply-value
)
85 (operator :initarg
:operator
86 :reader condition-operator
)
88 :reader condition-args
)
89 (reply :initarg
:reply
90 :reader condition-reply
))
91 (:report print-assertion
))
93 (defun print-assertion (condition stream
)
94 (format stream
" (~A "
95 (condition-operator condition
))
97 for rest on
(cons (condition-reply-value condition
)
98 (condition-args condition
))
99 for value
= (car rest
)
100 for more-p
= (cdr rest
)
101 do
(if (and (arrayp value
) (not (stringp value
)))
102 (format stream
"<array>")
103 (format stream
"~S" value
))
105 do
(princ #\Space stream
))
106 (format stream
")~%"))
108 (defun function-designator-p (thing)
109 "Return true value if THING is a function or a symbol that has a function definition."
110 (or (functionp thing
)
114 (defmacro with-operator-defaulting
((default-operator) &body body
)
115 "If OPERATOR is not a function designator, prepend it to ARGS and
116 bind OPERATOR to DEFAULT-OPERATOR. OPERATOR and ARGS are captured
117 from the expansion environment."
118 `(if (function-designator-p operator
)
120 (let ((operator ',default-operator
)
121 (args (cons operator args
)))
124 (defun http-assert (reply-slot-name operator
&rest args
)
125 (let ((reply-value (slot-value *last-reply
* reply-slot-name
)))
126 (with-operator-defaulting (equal)
127 (unless (apply operator reply-value args
)
128 (signal 'assertion-failed
129 :reply-slot-name reply-slot-name
130 :reply-value reply-value
133 :reply
*last-reply
*)))))
135 (define-condition header-assertion-failed
(assertion-failed)
136 ((header-name :initarg
:header-name
:reader condition-header-name
)))
138 (defun http-assert-header (header-name operator
&rest args
)
139 (let ((header-value (cdr (assoc header-name
(slot-value *last-reply
* 'headers
) :test
#'string-equal
))))
140 (with-operator-defaulting (matches)
141 (unless (apply operator header-value args
)
142 (signal 'header-assertion-failed
143 :reply-slot-name
'headers
144 :header-name header-name
145 :reply-value header-value
148 :reply
*last-reply
*)))))
150 (defun http-assert-body (regex)
151 (http-assert 'body
'matches regex
))
153 (defun matches (string regex
)
154 (cl-ppcre:scan regex string
))
156 (defun integer-equal (string integer
)
157 (eql (parse-integer string
) integer
))
159 (defun http-request (url
161 &key
(protocol :http
/1.1)
172 (declare (ignore protocol method content content-type content-length cookie-jar basic-authorization
173 range parameters external-format-out additional-headers
))
174 (setf *last-reply
* (make-instance 'http-reply
))
175 (with-slots (body status-code headers uri stream close
) *last-reply
*
176 (setf (values body status-code headers uri stream close
)
177 (apply 'drakma
:http-request
178 (format nil
"~A~A" (script-context-base-url *script-context
*) url
)