Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

E2e tests for auth and forms running on GH Actions #30 #32

Merged
merged 16 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/form-validation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"dependencies": {
"filewatcher": "^3.0.1",
"minimal-stylesheet": "^0.1.0",
"nbb": "0.0.97",
"nbb": "^1.2.180",
"node-input-validator": "^4.4.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
Expand Down
2 changes: 1 addition & 1 deletion examples/form-validation/webserver.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
(email-form data))
(.send res rendered-html)))

(defn handle-csrf-error [err req res n]
(defn handle-csrf-error [err _req res n]
(if (= (aget err "code") "EBADCSRFTOKEN")
(-> res
(.status 403)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"docs": "nbb bin/generate-docs.cljs",
"pre-publish": "npm run sync-deps; nbb bin/update-readme-versions.cljs; npm run docs; echo Changes:; git log --oneline `git rev-list --tags --max-count=1`..; echo; echo 'Now commit changes and run `git tag vX.Y.Z`.'",
"test": "rm -f ./tests.sqlite; SECRET=testing TESTING=1 DATABASE_URL=sqlite://./tests.sqlite npx shadow-cljs compile test",
"test-e2e": "nbb --classpath src src/sitefoxtest/e2etests.cljs",
"test-e2e": "NODE_OPTIONS='--experimental-fetch --no-warnings' nbb --classpath src src/sitefoxtest/e2etests.cljs",
"watch": "SECRET=watching TESTING=1 shadow-cljs watch test"
}
}
262 changes: 233 additions & 29 deletions src/sitefoxtest/e2etests.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,21 @@

;(def browser-type pw/chromium)

(def host "127.0.0.1")
(def host "localhost")
(def base-url (str "http://" host ":8000"))
(def browser-timeout 60000)

(def log (j/call-in js/console [:log :bind] js/console " ---> "))
(def log-listeners (atom #{}))

(j/assoc! env "BIND_ADDRESS" host)

(defn run-server [path server-command port]
; first run npm init in the folder
(log "Installing server deps.")
(spawnSync "npm i --no-save" #js {:cwd path
:stdio "inherit"
:shell true})
; delete any old database hanging around
(spawnSync "rm -f database.sqlite"
#js {:cwd path
:stdio "inherit"
:shell true})
; now run the server
(log "Spawning server.")
(p/let [server (spawn server-command #js {:cwd path
Expand All @@ -35,10 +36,13 @@
:detach true})
port-info (wait-for-port #js {:host host :port port})
pid (j/get server :pid)]
(log "Setting up stdout listener.")
(j/call-in server [:stdout :on] "data"
(fn [data]
;(print data)
(doseq [[re-string listener-fn] @log-listeners]
(let [matches (.match (.toString data) (js/RegExp. re-string "s"))]
(let [matches (.match (.toString data)
(js/RegExp. re-string "s"))]
(when matches
(listener-fn matches)
(swap! log-listeners disj [re-string listener-fn]))))
Expand All @@ -56,18 +60,20 @@
(defn get-browser []
(p/let [browser (.launch pw/chromium
#js {:headless (not (nil? (j/get env "CI")))
:timeout 3000})
:timeout browser-timeout})
context (.newContext browser)
page (.newPage context)]
(.setDefaultTimeout page 3000)
{:browser browser :context context :page page}))

(defn catch-fail [err done server & [browser]]
(log "Caught test error.")
(when err
(.error js/console (j/get err :stack)))
(.error js/console err))
(is (nil? err)
(str "Error in test: " (.toString err)))
(j/call server :kill)
(when (and server (j/get server :kill))
(j/call server :kill))
(when browser
(.close browser))
(done))
Expand All @@ -76,14 +82,21 @@
(t/testing "Basic test of Sitefox on nbb."
(async done
(p/let [_ (log "Test: basic-site-test")
server (run-server "examples/nbb" "npm i --no-save; npm run serve" 8000)]
server (run-server "examples/nbb"
"npm i --no-save; npm run serve"
8000)]
(p/catch
(p/let [res (js/fetch base-url)
text (.text res)]
(is (j/get-in server [:process :pid]) "Server is running?")
(is (j/get server :open) "Server port is open?")
(is (j/get res :ok) "Was server response ok?")
(is (includes? text "Hello") "Server response includes 'Hello' text?")
(log "Starting test checks.")
(is (j/get-in server [:process :pid])
"Server is running?")
(is (j/get server :open)
"Server port is open?")
(is (j/get res :ok)
"Was server response ok?")
(is (includes? text "Hello")
"Server response includes 'Hello' text?")
(log "Test done. Killing server.")
(j/call server :kill)
(log "After server.")
Expand All @@ -95,11 +108,55 @@
content (.content page)]
(is (includes? content text) message)))

(defn check-for-no-text [page text selector message]
(p/let [_ (-> page (.waitForSelector selector))
content (.content page)]
(is (not (includes? content text)) message)))

(defn check-failed-sign-in
[page password]
(p/do!
; sign in again
(.goto page base-url)
; click "Sign in"
(-> page (.locator "a[href='/auth/sign-in']") .click)

; do a failed sign in
(-> page (.locator "input[name='email']")
(.fill "[email protected]"))
(-> page (.locator "input[name='password']")
(.fill password))
(-> page
(.locator "button:has-text('Sign in')")
.click)
(check-for-text page "Invalid email or password"
"Incorrect password shows a message.")))

(defn sign-out [page]
; click "Sign out"
(-> page (.locator "a[href='/auth/sign-out']") .click)
(check-for-no-text page "Signed in" "a[href='/auth/sign-in']"
"User is correctly signed out on homepage."))

(defn sign-in [page password]
(p/do!
; successful sign in
(-> page (.locator "input[name='password']")
(.fill password))
(-> page
(.locator "button:has-text('Sign in')")
.click)
(check-for-text
page "Signed in"
"User is correctly signed in again.")))

(deftest nbb-auth
(t/testing "Auth against Sitefox on nbb tests."
(async done
(p/let [_ (log "Test: nbb-auth")
server (run-server "examples/nbb-auth" "npm i --no-save; npm run serve" 8000)
server (run-server "examples/nbb-auth"
"npm i --no-save; npm run serve"
8000)
{:keys [page browser]} (get-browser)]
(p/catch
(p/do!
Expand All @@ -109,19 +166,81 @@
; click "Sign up"
(-> page (.locator "a[href='/auth/sign-up']") .click)
; fill out details and sign up
(-> page (.locator "input[name='email']") (.fill "[email protected]"))
(-> page (.locator "input[name='email2']") (.fill "[email protected]"))
(-> page (.locator "input[name='password']") (.fill "tester"))
(-> page (.locator "input[name='password2']") (.fill "tester"))
(-> page (.locator "input[name='email']")
(.fill "[email protected]"))
(-> page (.locator "input[name='email2']")
(.fill "[email protected]"))
(-> page (.locator "input[name='password']")
(.fill "tester"))
(-> page (.locator "input[name='password2']")
(.fill "tester"))

(p/let [[log-items] (p/all [(listen-to-log "verify-url (?<url>http.*?)[\n$]")
(-> page (.locator "button:has-text('Sign up')") .click)])
(p/let [[log-items]
(p/all [(listen-to-log
"verify-url (?<url>http.*?)[\n$]")
(-> page
(.locator "button:has-text('Sign up')")
.click)])
url (j/get-in log-items [:groups :url])]
; click on the verification link
(.goto page url)
(check-for-text page "Signed in" "User is correctly signed in after verification.")
(check-for-text
page "Signed in"
"User is correctly signed in after verification.")
(.goto page base-url)
(check-for-text page "Signed in" "User is correctly signed in on homepage."))
(check-for-text
page "Signed in"
"User is correctly signed in on homepage."))

(print "sign out")
(sign-out page)

(print "check failed sign in")
(check-failed-sign-in page "testerwrong")

(print "sign in again")
(sign-in page "tester")

(print "forgot password")
; test forgot password flow
(-> page (.locator "a[href='/auth/sign-out']") .click)
; click "Sign in"
(-> page (.locator "a[href='/auth/sign-in']") .click)
; click "Forgot password link"
(-> page (.locator "a[href='/auth/reset-password']") .click)
; fill out the forgot password form
(-> page (.locator "input[name='email']")
(.fill "[email protected]"))

(p/let [[log-items]
(p/all [(listen-to-log
"verify-url (?<url>http.*?)[\n$]")
(-> page
(.locator
"button:has-text('Reset password')")
.click)])
url (j/get-in log-items [:groups :url])]
(check-for-text page "Reset password link sent"
"User has been notified of reset email.")
(.goto page url))

; enter updated passwords
(-> page (.locator "input[name='password']")
(.fill "testagain"))
(-> page (.locator "input[name='password2']")
(.fill "testagain"))
(-> page
(.locator "button:has-text('Update password')")
.click)
(check-for-text
page "Signed in"
"User is correctly signed in again.")

; check sign in fails with old password
(sign-out page)
(check-failed-sign-in page "tester")
; check successful sign in with new password
(sign-in page "testagain")

(log "Closing resources.")
(j/call server :kill)
Expand All @@ -130,10 +249,95 @@
(done))
#(catch-fail % done server browser))))))

#_ (defmethod cljs.test/report [:cljs.test/default :end-run-tests] [m]
(print "report")
(if (cljs.test/successful? m)
(println "Success!")
(println "FAIL")))
(defn check-form-submit [page]
(p/do!
; fill out bad form details
(-> page (.locator "input[name='name']")
(.fill "Bilbo"))
(-> page (.locator "input[name='date']")
(.fill "2023-06-01"))
(-> page (.locator "input[name='count']")
(.fill "7"))
(-> page (.locator "button[type='submit']") .click)
(check-for-text
page "Form complete."
"Form submits sucessfully.")))

(deftest nbb-forms
(t/testing "Sitefox forms and CSRF on nbb tests."
(async done
(p/let [_ (log "Test: nbb-forms")
server (run-server "examples/form-validation"
"npm i --no-save; npm run serve"
8000)
{:keys [page context browser]} (get-browser)]
(p/catch
(p/do!
(.goto page base-url)

; fill out bad form details
(-> page (.locator "input[name='name']")
(.fill ""))
(-> page (.locator "input[name='date']")
(.fill "SEPTEMBER THE NOTHING"))
(-> page (.locator "input[name='count']")
(.fill "XYZ"))

(-> page (.locator "button[type='submit']") .click)

(check-for-text
page "You must enter a name between 5 and 20 characters."
"Name validation failed successfully.")

(check-for-text
page "You must enter a valid date in YYYY-MM-DD format."
"Date validation failed successfully.")

(check-for-text
page "You must enter a quantity between 5 and 10."
"Count validation failed successfully.")

; fill out form correctly

(.goto page base-url)

(check-form-submit page)

; fill out form correctly but fail csrf
(.goto page base-url)

; fill out bad form details
(-> page (.locator "input[name='name']")
(.fill "Bilbo"))
(-> page (.locator "input[name='date']")
(.fill "2023-06-01"))
(-> page (.locator "input[name='count']")
(.fill "7"))
; modify csrf field
(-> page
(.evaluate "document.querySelector('input[name=\"_csrf\"]').value='BOGUS'"))

(-> page (.locator "button[type='submit']") .click)

(check-for-text
page "The form was tampered with."
"CSRF error caught sucessfully.")

; sanity check by running multiple CSRF checks in parallel forms

; open another form to get a new csrf token
(p/let [page2 (.newPage context)]
(.goto page2 base-url)
; then reload the first page to get a new token
(.goto page (str base-url "?hello=1"))
; check the second tab can still successfully submit
(check-form-submit page2))

(log "Closing resources.")
(j/call server :kill)
(.close browser)
(log "Resources closed.")
(done))
#(catch-fail % done server browser))))))

(t/run-tests *ns*)
Loading