Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Create test suite

Add travis-ci.
Use kerodon for integration tests.
Some test fake an scp request and jump straight to scp/nail using
nailgun-shim.
Some src changes were required to make testable and test pass.
  • Loading branch information...
commit 48543b468639da652a65c87abbf9e6245297ebf5 1 parent 34d27b9
Nelson Morris xeqi authored
5 .gitignore
View
@@ -1,7 +1,8 @@
*.class
-*.jar
*.swp
-#*
+\#*
+\.#*
+lib/
home
data/
native/
3  .travis.yml
View
@@ -0,0 +1,3 @@
+language: clojure
+before_script:
+ - "mkdir data && sqlite3 data/test_db < clojars.sql"
5 project.clj
View
@@ -15,6 +15,9 @@
[org.clojars.ato/nailgun "0.7.1"]
[org.xerial/sqlite-jdbc "3.6.17"]
[org.apache.commons/commons-email "1.2"]]
- :dev-dependencies [[lein-ring "0.4.5"]]
+ :dev-dependencies [[lein-ring "0.4.5"]
+ [enlive "1.0.0"]
+ [kerodon "0.0.1-SNAPSHOT"]
+ [nailgun-shim "0.0.1"]]
:ring {:handler clojars.web/clojars-app})
50 src/clojars/db.clj
View
@@ -34,6 +34,9 @@
[n]
(apply str (repeatedly n #(rand-nth constituent-chars))))
+(defn ^:dynamic get-time []
+ (Date.))
+
(defn write-key-file [path]
(locking (:key-file config)
(let [new-file (File. (str path ".new"))]
@@ -47,15 +50,13 @@
"\n")))))
(.renameTo new-file (File. path)))))
-(defn db-middleware
- [handler]
- (fn [request]
- (sql/with-connection (:db config) (handler request))))
-
(defmacro with-db
[& body]
- `(sql/with-connection (:db config)
- ~@body))
+ ;;TODO does connection sharing break something when deployed?
+ `(if (sql/find-connection)
+ (do ~@body)
+ (sql/with-connection (:db config)
+ ~@body)))
(defn bcrypt [s]
(BCrypt/hashpw s (BCrypt/gensalt (:bcrypt-work-factor config))))
@@ -90,7 +91,7 @@
(defn auth-user [user plaintext]
(sql/with-query-results rs
- ["select * from users where (user = ? or email = ?)" user user]
+ ["select * from users where (user = ? or email = ?)" user user]
(first (filter (partial authed? plaintext) rs))))
(defn jars-by-user [user]
@@ -100,15 +101,15 @@
(defn jars-by-group [group]
(sql/with-query-results rs [(str "select * from jars where "
- "group_name = ? group by jar_name")
- group]
+ "group_name = ? group by jar_name")
+ group]
(vec rs)))
(defn recent-jars []
(sql/with-query-results rs
[(str "select * from jars group by group_name, jar_name "
"order by created desc limit 5")]
- (vec rs)))
+ (vec rs)))
(defn find-canon-jar [jarname]
(sql/with-query-results rs
@@ -116,24 +117,25 @@
"jar_name = ? and group_name = ? "
"order by created desc limit 1")
jarname jarname]
- (first rs)))
+ (first rs)))
(defn find-jar
([jarname]
- (sql/with-query-results rs [(str "select * from jars where "
- "jar_name = ?") jarname]
+ (sql/with-query-results rs
+ [(str "select * from jars where jar_name = ?") jarname]
(first rs)))
([group jarname]
- (sql/with-query-results rs [(str "select * from jars where group_name = ? and "
- "jar_name = ? order by created desc "
- "limit 1") group jarname]
- (first rs))))
+ (sql/with-query-results rs
+ [(str "select * from jars where group_name = ? and "
+ "jar_name = ? order by created desc "
+ "limit 1") group jarname]
+ (first rs))))
(defn add-user [email user password ssh-key]
(sql/insert-values :users
;; TODO: remove salt field
[:email :user :password :salt :ssh_key :created]
- [email user (bcrypt password) "" ssh-key (Date.)])
+ [email user (bcrypt password) "" ssh-key (get-time)])
(sql/insert-values :groups
[:name :user]
[(str "org.clojars." user) user])
@@ -169,9 +171,7 @@
(when-not (re-matches #"^[a-z0-9-_.]+$" (:name jarmap))
(throw (Exception. (str "Jar names must consist solely of lowercase "
"letters, numbers, hyphens and underscores."))))
-
- (sql/with-connection (:db config)
- (sql/transaction
+ (sql/transaction
(when check-only (sql/set-rollback-only))
(check-and-add-group account (:group jarmap) (:name jarmap))
(sql/insert-records :jars
@@ -179,11 +179,11 @@
:jar_name (:name jarmap)
:version (:version jarmap)
:user account
- :created (Date.)
+ :created (get-time)
:description (:description jarmap)
:homepage (:homepage jarmap)
:authors (str/join ", " (map #(.replace % "," "")
- (:authors jarmap)))}))))
+ (:authors jarmap)))})))
(defn quote-hyphenated
"Wraps hyphenated-words in double quotes."
@@ -196,7 +196,7 @@
(sql/with-query-results rs
[(str "select jar_name, group_name from search where "
"content match ? "
- "order by rowid desc "
+ "order by rowid desc "
"limit 100 "
"offset ?")
(quote-hyphenated query)
10 src/clojars/maven.clj
View
@@ -21,7 +21,11 @@
;; TODO: find out if it's safe to just leave these hanging around like
;; this
(def embedder (doto (Embedder.) (.start)))
-(def container (.getContainer embedder))
+(def container (let [container (.getContainer embedder)]
+ (-> container
+ (.getLoggerManager)
+ (.setThreshold org.codehaus.plexus.logging.Logger/LEVEL_DISABLED))
+ container))
(defn model-to-map [model]
{:name (.getArtifactId model)
@@ -94,5 +98,7 @@
(defn deploy-model [jarfile model repo-path]
(.deploy
(.lookup container ArtifactDeployer/ROLE)
- jarfile (make-artifact model) (make-repo "clojars" repo-path)
+ jarfile
+ (make-artifact model)
+ (make-repo "clojars" repo-path)
(make-local-repo)))
92 src/clojars/scp.clj
View
@@ -99,60 +99,60 @@
:let [names (jar-names jarmap)]]
(if-let [jarfile (some jarfiles names)]
(do
- (.println *err* (str "\nDeploying " (:group jarmap) "/"
+ (.println (.err ctx) (str "\nDeploying " (:group jarmap) "/"
(:name jarmap) " " (:version jarmap)))
- (db/add-jar account jarmap true)
- (maven/deploy-model jarfile model
- (str "file://" (:repo config)))
- (db/add-jar account jarmap))
+ (db/with-db
+ (db/add-jar account jarmap true)
+ (maven/deploy-model jarfile model
+ (.toString (.toURI (File. (:repo config)))))
+ (db/add-jar account jarmap)))
(throw (Exception. (str "You need to give me one of: " names)))))
- (.println *err* (str "\nSuccess! Your jars are now available from "
+ (.println (.err ctx) (str "\nSuccess! Your jars are now available from "
"http://clojars.org/"))
(.flush (.err ctx))))
(defn nail [#^NGContext ctx]
(let [old-out System/out]
(try
- (System/setOut (.err ctx))
- (let [in (.in ctx)
- err (.err ctx)
- out (.out ctx)
- account (first (.getArgs ctx))]
-
- (when-not account
- (throw (Exception. "I don't know who you are!")))
-
- (doto (.err ctx)
- (.println (str "Welcome to Clojars, " account "!"))
- (.flush))
-
- (loop [files [], okay true]
- (when (> (count files) 100)
- (throw (IOException. "Too many files uploaded at once")))
-
- (when okay
- (send-okay ctx))
-
- (let [cmd (.read in)]
- (if (= -1 cmd)
- (finish-deploy ctx files)
- (let [cmd (char cmd)]
- ;; TODO: use core.match
- (condp = cmd
- (char 0) (recur files false)
- \C (recur (conj files (scp-copy ctx)) true)
- \D (do (safe-read-line in) (recur files true))
- \T (do (safe-read-line in) (recur files true))
- \E (do (safe-read-line in) (recur files true))
- (throw (IOException. (str "Unknown scp command: '"
- (int cmd) "'")))))))))
-
- (catch Throwable t
- ;; (.printStackTrace t *err*)
- (.println (.err ctx) (str "Error: " (.getMessage t)))
- (.flush (.err ctx))
- (throw t))
- (finally (System/setOut old-out)))))
+ (System/setOut (.err ctx))
+ (let [in (.in ctx)
+ err (.err ctx)
+ account (first (.getArgs ctx))]
+
+ (when-not account
+ (throw (Exception. "I don't know who you are!")))
+
+ (doto (.err ctx)
+ (.println (str "Welcome to Clojars, " account "!"))
+ (.flush))
+
+ (loop [files [], okay true]
+ (when (> (count files) 100)
+ (throw (IOException. "Too many files uploaded at once")))
+
+ (when okay
+ (send-okay ctx))
+
+ (let [cmd (.read in)]
+ (if (= -1 cmd)
+ (finish-deploy ctx files)
+ (let [cmd (char cmd)]
+ ;; TODO: use core.match
+ (condp = cmd
+ (char 0) (recur files false)
+ \C (recur (conj files (scp-copy ctx)) true)
+ \D (do (safe-read-line in) (recur files true))
+ \T (do (safe-read-line in) (recur files true))
+ \E (do (safe-read-line in) (recur files true))
+ (throw (IOException. (str "Unknown scp command: '"
+ (int cmd) "'")))))))))
+
+ (catch Throwable t
+ ;; (.printStackTrace t *err*)
+ (.println (.err ctx) (str "Error: " (.getMessage t)))
+ (.flush (.err ctx))
+ (throw t))
+ (finally (System/setOut old-out)))))
(defn -nailMain [context]
(nail context))
8 src/clojars/web.clj
View
@@ -1,6 +1,6 @@
(ns clojars.web
(:use [clojars.db :only [with-db group-members find-user add-member
- find-jar find-canon-jar db-middleware]]
+ find-jar find-canon-jar]]
[clojars.web.dashboard :only [dashboard index-page]]
[clojars.web.search :only [search]]
[clojars.web.user :only [profile-form update-profile show-user
@@ -84,6 +84,7 @@
(try-account
(show-user account user))
:next))
+
(GET ["/:jarname", :jarname #"[^/]+"] {session :session {jarname "jarname"} :params}
(if-let [jar (with-db (find-canon-jar jarname))]
(try-account
@@ -103,6 +104,11 @@
(ANY "*" {session :session}
(html-doc (session :account) "Page not found" (not-found-doc))))
+(defn db-middleware
+ [handler]
+ (fn [request]
+ (with-db (handler request))))
+
(def clojars-app
(-> main-routes
wrap-session
11 src/clojars/web/user.clj
View
@@ -56,7 +56,7 @@
(seq (group-members user))))
"Username is already taken")
(conj-when (not (re-matches #"[a-z0-9_-]+" user))
- (str "Usernames must consist only of lowercase "
+ (str "Username must consist only of lowercase "
"letters, numbers, hyphens and underscores."))
(conj-when (not (or (blank? ssh-key)
(valid-ssh-key? ssh-key)))
@@ -108,9 +108,12 @@
[:h1 "Forgot password?"]
(form-to [:post "/forgot-password"]
(label :email-or-username "Email or username:")
- (text-field :email-or-username "")
+ (text-field :email-or-username)
(submit-button "Send new password"))))
+(defn ^{:dynamic true} send-out [email]
+ (.send email))
+
;; TODO: move this to another file?
(defn send-mail [to subject message]
(let [{:keys [hostname username password port ssl from]} (config/config :mail)
@@ -124,8 +127,8 @@
(.setSubject subject)
(.setMsg message))]
(when (and username password)
- (.setAuthentication username password))
- (.send mail)))
+ (.setAuthentication mail username password))
+ (send-out mail)))
(defn forgot-password [{email-or-username "email-or-username"}]
(when-let [user (find-user-by-user-or-email email-or-username)]
13 test-resources/config.clj
View
@@ -0,0 +1,13 @@
+{:db {:classname "org.sqlite.JDBC"
+ :subprotocol "sqlite"
+ :subname "data/test_db"}
+ :key-file "data/test_authorized_keys"
+ :repo "data/test_repo"
+ :bcrypt-work-factor 12
+ :mail {:hostname "smtp.gmail.com"
+ :from "noreply@clojars.org"
+ :username "clojars@pupeno.com"
+ :password "fuuuuuu"
+ :port 465 ; If you change ssl to false, the port might not be effective, search for .setSSL and .setSslSmtpPort
+ :ssl true}}
+
1  test-resources/fake.jar
View
@@ -0,0 +1 @@
+bad jar
15 test-resources/fake.pom
View
@@ -0,0 +1,15 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>fake</groupId>
+ <artifactId>fake</artifactId>
+ <version>0.0.1</version>
+ <packaging>jar</packaging>
+
+ <name>asdf</name>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+</project>
1  test-resources/test.jar
View
@@ -0,0 +1 @@
+bad jar
15 test-resources/test.pom
View
@@ -0,0 +1,15 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>fake</groupId>
+ <artifactId>test</artifactId>
+ <version>0.0.1</version>
+ <packaging>jar</packaging>
+
+ <name>asdf</name>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+</project>
16 test/clojars/test/integration/responses.clj
View
@@ -0,0 +1,16 @@
+(ns clojars.test.integration.responses
+ (:use clojure.test
+ kerodon.core
+ clojars.test.integration.steps)
+ (:require [clojars.web :as web]
+ [clojars.test.test-helper :as help]
+ [net.cgrand.enlive-html :as enlive]))
+
+(help/use-fixtures)
+
+(deftest respond-404
+ (-> (init :ring-app web/clojars-app)
+ (request :get "/nonexistant-route")
+ (validate (status (is= 200))
+ (html #(is (= [["Page not found | Clojars"]]
+ (map :content (enlive/select % [:title]))))))))
36 test/clojars/test/integration/sessions.clj
View
@@ -0,0 +1,36 @@
+(ns clojars.test.integration.sessions
+ (:use clojure.test
+ kerodon.core
+ clojars.test.integration.steps)
+ (:require [clojars.web :as web]
+ [clojars.test.test-helper :as help]
+ [net.cgrand.enlive-html :as enlive]))
+
+(help/use-fixtures)
+
+(deftest user-cant-login-with-bad-user-pass-combo
+ (-> (init :ring-app web/clojars-app)
+ (login-as "fixture@example.org" "password")
+ (validate (status (is= 200))
+ (html #(is (= ["Incorrect username or password."]
+ (map enlive/text
+ (enlive/select % [:article :div.error]))))))))
+
+(deftest user-can-login-and-logout
+ (-> (init :ring-app web/clojars-app)
+ (register-as "fixture" "fixture@example.org" "password" ""))
+ (doseq [login ["fixture@example.org" "fixture"]]
+ (-> (init :ring-app web/clojars-app)
+ (login-as login "password")
+ (follow-redirect)
+ (validate (status (is= 200))
+ (html #(is (= ["Dashboard (fixture)"]
+ (map enlive/text
+ (enlive/select % [:article :h1]))))))
+ (follow "logout")
+ (follow-redirect)
+ (validate
+ (status (is= 200))
+ (html #(is (= "login"
+ (first (map enlive/text
+ (enlive/select % [:nav :a]))))))))))
64 test/clojars/test/integration/steps.clj
View
@@ -0,0 +1,64 @@
+(ns clojars.test.integration.steps
+ (:use kerodon.core)
+ (:require [clojure.test :as test]
+ [clojure.java.io :as io]
+ [clojars.scp :as scp]
+ [clojars.config :as config]))
+
+(defn is= [val]
+ (fn [v2] (test/is (= val v2))))
+
+(defn log [state f]
+ (prn (f state))
+ state)
+
+(defn login-as [state user password]
+ (-> state
+ (request :get "/")
+ (follow "login")
+ (fill-in "Username or email:" user)
+ (fill-in "Password:" password)
+ (press "Login")))
+
+(defn register-as
+ ([state user email password ssh-key]
+ (register-as state user email password password ssh-key))
+ ([state user email password confirm ssh-key]
+ (-> state
+ (request :get "/")
+ (follow "register")
+ (fill-in "Email:" email)
+ (fill-in "Username:" user)
+ (fill-in "Password:" password)
+ (fill-in "Confirm password:" confirm)
+ (fill-in "SSH public key:" ssh-key)
+ (press "Register"))))
+
+(defn scp-str-send-resource [filename]
+ (let [contents (slurp (io/resource filename))]
+ (str "C0644 " (count contents) " " filename "\n" contents)))
+
+(def valid-ssh-key "ssh-test 0")
+
+(defn find-user [key]
+ (let [[string user]
+ (re-find
+ (re-pattern
+ (str "command=\"ng --nailgun-port 8700 clojars.scp (.*)\",no-agent-forwarding,no-port-forwarding,no-pty,no-X11-forwarding " key "\n"))
+ (slurp (:key-file config/config)))]
+ user))
+
+(defn scp [key & resources]
+ (let [shim (proxy [com.martiansoftware.nailgun.NGContextShim] [])
+ user (find-user key)
+ bytes (java.io.ByteArrayOutputStream.)
+ in (java.io.ByteArrayInputStream.
+ (.getBytes (apply str (map scp-str-send-resource resources))
+ "UTF-8"))
+ out (java.io.PrintStream. (java.io.ByteArrayOutputStream.))]
+ (set! (.err shim) (java.io.PrintStream. bytes))
+ (set! (.in shim) in)
+ (set! (.out shim) out)
+ (.setArgs shim (into-array [user]))
+ (scp/nail shim)
+ (.toString bytes)))
294 test/clojars/test/integration/users.clj
View
@@ -0,0 +1,294 @@
+(ns clojars.test.integration.users
+ (:use clojure.test
+ kerodon.core
+ clojars.test.integration.steps)
+ (:require [clojars.web :as web]
+ [clojars.test.test-helper :as help]
+ [net.cgrand.enlive-html :as enlive]))
+
+(help/use-fixtures)
+
+(deftest user-can-register
+ (-> (init :ring-app web/clojars-app)
+ (register-as "dantheman" "test@example.org" "password" "")
+ (follow-redirect)
+ ;;TODO: should the user already be signed in?
+ (validate (status (is= 200))
+ ;; (html #(is (= ["Dashboard (dantheman)"]
+ ;; (map enlive/text
+ ;; (enlive/select % [:article :h1])))))
+ )))
+
+(deftest bad-registration-info-should-show-error
+ (-> (init :ring-app web/clojars-app)
+ (register-as "fixture" "fixture@example.org" "password" ""))
+ (-> (init :ring-app web/clojars-app)
+ (request :get "/")
+ (follow "register")
+ (validate (status (is= 200))
+ (html #(is (= [["Register | Clojars"]]
+ (map :content (enlive/select % [:title]))))))
+ (fill-in "Email:" "test@example.org")
+ (fill-in "Username:" "dantheman")
+ (press "Register")
+ (validate (status (is= 200))
+ (html #(is (= ["Password can't be blank"]
+ (map enlive/text (enlive/select % [:article :div.error :ul :li]))))))
+ (fill-in "Password:" "password")
+ (press "Register")
+ (validate (status (is= 200))
+ (html #(is (= ["Password and confirm password must match"]
+ (map enlive/text (enlive/select % [:article :div.error :ul :li]))))))
+ (fill-in "Email:" "")
+ (fill-in "Password:" "password")
+ (fill-in "Confirm password:" "password")
+ (press "Register")
+ (validate (status (is= 200))
+ (html #(is (= ["Email can't be blank"]
+ (map enlive/text (enlive/select % [:article :div.error :ul :li]))))))
+ (fill-in "Email:" "test@example.org")
+ (fill-in "Username:" "")
+ (fill-in "Password:" "password")
+ (fill-in "Confirm password:" "password")
+ (press "Register")
+ (validate (status (is= 200))
+ (html #(is (= ["Username must consist only of lowercase letters, numbers, hyphens and underscores."
+ "Username can't be blank"]
+ (map enlive/text (enlive/select % [:article :div.error :ul :li]))))))
+ (fill-in "Username:" "<script>")
+ (fill-in "Password:" "password")
+ (fill-in "Confirm password:" "password")
+ (press "Register")
+ (validate (status (is= 200))
+ (html #(is (= ["Username must consist only of lowercase letters, numbers, hyphens and underscores."]
+ (map enlive/text (enlive/select % [:article :div.error :ul :li]))))))
+ (fill-in "Username:" "fixture")
+ (fill-in "Password:" "password")
+ (fill-in "Confirm password:" "password")
+ (press "Register")
+ (validate (status (is= 200))
+ (html #(is (= ["Username is already taken"]
+ (map enlive/text (enlive/select % [:article :div.error :ul :li]))))))
+ (fill-in "Username:" "dantheman")
+ (fill-in "Password:" "password")
+ (fill-in "Confirm password:" "password")
+ (fill-in "SSH public key:" "asdf")
+ (press "Register")
+ (validate (status (is= 200))
+ (html #(is (= ["Invalid SSH public key"]
+ (map enlive/text (enlive/select % [:article :div.error :ul :li]))))))))
+
+
+(deftest user-can-update-info
+ (-> (init :ring-app web/clojars-app)
+ (register-as "fixture" "fixture@example.org" "password" ""))
+ (-> (init :ring-app web/clojars-app)
+ (login-as "fixture" "password")
+ (follow-redirect)
+ (follow "profile")
+ (fill-in "Email:" "fixture2@example.org")
+ (fill-in "Password:" "password2")
+ (fill-in "Confirm password:" "password2")
+ (press "Update")
+ (follow-redirect)
+ (follow "logout")
+ (follow-redirect)
+ (validate
+ (status (is= 200))
+ (html #(is (= "login"
+ (first (map enlive/text
+ (enlive/select % [:nav :a])))))))
+ (login-as "fixture2@example.org" "password2")
+ (follow-redirect)
+ (validate (status (is= 200))
+ (html #(is (= ["Dashboard (fixture)"]
+ (map enlive/text
+ (enlive/select % [:article :h1]))))))))
+
+(deftest bad-update-info-should-show-error
+ (-> (init :ring-app web/clojars-app)
+ (register-as "fixture" "fixture@example.org" "password" "")
+ (login-as "fixture" "password")
+ (follow-redirect)
+ (follow "profile")
+ (validate (status (is= 200))
+ (html #(is (= [["Profile | Clojars"]]
+ (map :content (enlive/select % [:title]))))))
+ (fill-in "Email:" "fixture2@example.org")
+ (press "Update")
+ (validate (status (is= 200))
+ (html #(is (= ["Password can't be blank"]
+ (map enlive/text (enlive/select % [:article :div.error :ul :li]))))))
+ (fill-in "Password:" "password")
+ (press "Update")
+ (validate (status (is= 200))
+ (html #(is (= ["Password and confirm password must match"]
+ (map enlive/text (enlive/select % [:article :div.error :ul :li]))))))
+ (fill-in "Email:" "")
+ (fill-in "Password:" "password")
+ (fill-in "Confirm password:" "password")
+ (press "Update")
+ (validate (status (is= 200))
+ (html #(is (= ["Email can't be blank"]
+ (map enlive/text (enlive/select % [:article :div.error :ul :li]))))))
+ (fill-in "Password:" "password")
+ (fill-in "Confirm password:" "password")
+ (fill-in "SSH public key:" "asdf")
+ (press "Update")
+ (validate (status (is= 200))
+ (html #(is (= ["Invalid SSH public key"]
+ (map enlive/text (enlive/select % [:article :div.error :ul :li]))))))))
+
+(deftest user-can-get-new-password
+ (let [email (ref nil)]
+ (binding [clojars.web.user/send-out (fn [x] (dosync (ref-set email x)))]
+ (-> (init :ring-app web/clojars-app)
+ (register-as "fixture" "fixture@example.org" "password" ""))
+ (-> (init :ring-app web/clojars-app)
+ (request :get "/")
+ (follow "login")
+ (follow "Forgot password?")
+ (fill-in "Email or username:" "fixture")
+ (press "Send new password")
+ (validate (status (is= 200))
+ (html #(is (= ["If your account was found, you should get an email with a new password soon."]
+ (map enlive/text
+ (enlive/select % [:article :p])))))))
+ (is @email)
+ (let [from (.getFromAddress @email)]
+ (is (= (.getAddress from) "noreply@clojars.org"))
+ (is (= (.getPersonal from) "Clojars")))
+ (let [to (first (.getToAddresses @email))]
+ (is (= (.getAddress to) "fixture@example.org")))
+ (is (= (.getSubject @email)
+ "Password reset for Clojars"))
+ (.buildMimeMessage @email)
+ (let [[msg password] (re-find
+ #"Hello,\n\nYour new password for Clojars is: ([^ ]+)\n\nKeep it safe this time."
+ (.getContent (.getMimeMessage @email)))]
+ (is msg)
+ (-> (init :ring-app web/clojars-app)
+ (login-as "fixture@example.org" password)
+ (follow-redirect)
+ (validate (status (is= 200))
+ (html #(is (= ["Dashboard (fixture)"]
+ (map enlive/text
+ (enlive/select % [:article :h1])))))))))))
+
+(deftest user-can-register-and-scp
+ (-> (init :ring-app web/clojars-app)
+ (register-as "dantheman" "test@example.org" "password" valid-ssh-key))
+ (is (= "Welcome to Clojars, dantheman!\n\nDeploying fake/test 0.0.1\n\nSuccess! Your jars are now available from http://clojars.org/\n"
+ (scp valid-ssh-key "test.jar" "test.pom")))
+ (-> (init :ring-app web/clojars-app)
+ (request :get "/groups/fake")
+ (validate
+ (html #(is (= "dantheman"
+ (last (map enlive/text
+ (enlive/select %
+ [:ul :li :a])))))))))
+
+(deftest user-can-update-and-scp
+ (-> (init :ring-app web/clojars-app)
+ (register-as "dantheman" "test@example.org" "password" valid-ssh-key))
+ (let [new-ssh-key (str valid-ssh-key "0")]
+ (-> (init :ring-app web/clojars-app)
+ (login-as "dantheman" "password")
+ (follow-redirect)
+ (follow "profile")
+ (fill-in "Password:" "password")
+ (fill-in "Confirm password:" "password")
+ (fill-in "SSH public key:" new-ssh-key)
+ (press "Update"))
+ (is (= "Welcome to Clojars, dantheman!\n\nDeploying fake/test 0.0.1\n\nSuccess! Your jars are now available from http://clojars.org/\n"
+ (scp new-ssh-key "test.jar" "test.pom")))
+ (is (thrown? Exception (scp valid-ssh-key "test.jar" "test.pom")))))
+
+(deftest user-can-remove-key-and-scp-fails
+ (-> (init :ring-app web/clojars-app)
+ (register-as "dantheman" "test@example.org" "password" valid-ssh-key))
+ (-> (init :ring-app web/clojars-app)
+ (login-as "dantheman" "password")
+ (follow-redirect)
+ (follow "profile")
+ (fill-in "Password:" "password")
+ (fill-in "Confirm password:" "password")
+ (fill-in "SSH public key:" "")
+ (press "Update"))
+ (is (thrown? Exception (scp valid-ssh-key "test.jar" "test.pom"))))
+
+(deftest user-can-not-scp-to-group-they-are-not-a-member-of
+ (-> (init :ring-app web/clojars-app)
+ (register-as "dantheman" "test@example.org" "password" valid-ssh-key))
+ (scp valid-ssh-key "test.jar" "test.pom")
+ (let [ssh-key (str valid-ssh-key "1")]
+ (-> (init :ring-app web/clojars-app)
+ (register-as "fixture" "fixture@example.org" "password" ssh-key))
+ (is (thrown-with-msg? Exception
+ #"You don't have access to the fake group."
+ (scp ssh-key "test.jar" "test.pom")))))
+
+(deftest member-can-add-user-to-group
+ (-> (init :ring-app web/clojars-app)
+ (register-as "fixture" "fixture@example.org" "password" ""))
+ (-> (init :ring-app web/clojars-app)
+ (register-as "dantheman" "test@example.org" "password" "")
+ (login-as "dantheman" "password")
+ (request :get "/groups/org.clojars.dantheman")
+ (fill-in "#user" "fixture")
+ (press "add member")
+ ;;(follow-redirect)
+ (validate
+ (html #(is (= "fixture"
+ (first (map enlive/text
+ (enlive/select %
+ [:article :ul :li :a])))))))))
+
+(deftest user-must-exist-to-be-added-to-group
+ (-> (init :ring-app web/clojars-app)
+ (register-as "dantheman" "test@example.org" "password" "")
+ (login-as "dantheman" "password")
+ (request :get "/groups/org.clojars.dantheman")
+ (fill-in "#user" "fixture")
+ (press "add member")
+ (validate
+ (html #(is (= "No such user: fixture"
+ (first (map enlive/text
+ (enlive/select %
+ [:div.error :ul :li])))))))))
+
+;;TODO: (use pomegranate to)? verify scp'd file can be a dependency
+
+(deftest users-can-be-viewed
+ (-> (init :ring-app web/clojars-app)
+ (register-as "dantheman" "test@example.org" "password" "")
+ (request :get "/users/dantheman")
+ (validate
+ (html #(is (= "dantheman"
+ (last (map enlive/text
+ (enlive/select %
+ [:h1])))))))))
+
+(deftest jars-can-be-viewed
+ (-> (init :ring-app web/clojars-app)
+ (register-as "dantheman" "test@example.org" "password" valid-ssh-key))
+ (scp valid-ssh-key "test.jar" "test.pom")
+ (-> (init :ring-app web/clojars-app)
+ (request :get "/fake/test")
+ (validate
+ (html #(is (= "fake/test"
+ (last (map enlive/text
+ (enlive/select %
+ [:h1])))))))))
+
+(deftest canonical-jars-can-be-viewed
+ (-> (init :ring-app web/clojars-app)
+ (register-as "dantheman" "test@example.org" "password" valid-ssh-key))
+ (scp valid-ssh-key "fake.jar" "fake.pom")
+ (-> (init :ring-app web/clojars-app)
+ (request :get "/fake")
+ (validate
+ (html #(is (= "fake"
+ (last (map enlive/text
+ (enlive/select %
+ [:h1])))))))))
31 test/clojars/test/test_helper.clj
View
@@ -0,0 +1,31 @@
+(ns clojars.test.test-helper
+ (import java.io.File)
+ (:require [clojars.db :as db]
+ [clojars.config :as config]
+ [clojure.java.jdbc :as sql]
+ [clojure.test :as test]
+ [clojure.java.io :as io]))
+
+(defn delete-file-recursively
+ "Delete file f. If it's a directory, recursively delete all its contents.
+Raise an exception if any deletion fails unless silently is true."
+ [f]
+ (let [f (io/file f)]
+ (when (.isDirectory f)
+ (doseq [child (.listFiles f)]
+ (delete-file-recursively child)))
+ (io/delete-file f)))
+
+(defn use-fixtures []
+ (test/use-fixtures :once
+ (fn [f]
+ (db/with-db (f))))
+
+ (test/use-fixtures :each
+ (fn [f]
+ (let [file (File. (:repo config/config))]
+ (when (.exists file)
+ (delete-file-recursively file)))
+ (sql/transaction
+ (sql/set-rollback-only)
+ (f)))))
289 test/clojars/test/unit/db.clj
View
@@ -0,0 +1,289 @@
+(ns clojars.test.unit.db
+ (:use clojure.test)
+ (:require [clojars.db :as db]
+ [clojure.java.jdbc :as jdbc]
+ [clojars.test.test-helper :as help]))
+
+(help/use-fixtures)
+
+(defn submap [s m]
+ (every? (fn [[k v]] (= (m k) v)) s))
+
+(deftest added-users-can-be-found-and-authenticated
+ (let [email "test@example.com"
+ name "testuser"
+ password "password"
+ ssh-key "asdf"
+ ms (long 0)]
+ (is (db/add-user email name password ssh-key))
+ (are [x] (submap {:email email
+ :user name
+ :ssh_key ssh-key}
+ x)
+ (db/find-user name)
+ (db/find-user-by-user-or-email name)
+ (db/find-user-by-user-or-email email)
+ (db/auth-user name password)
+ (db/auth-user email password))))
+
+(deftest user-does-not-exist-and-cannot-be-authenticated
+ (is (not (db/find-user-by-user-or-email "test2@example.com")))
+ (is (not (db/auth-user "test2@example.com" "anything"))))
+
+(deftest updated-users-can-be-found-and-authenticated
+ (let [email "test@example.com"
+ name "testuser"
+ password "password"
+ ssh-key "asdf"
+ ms (long 0)
+ email2 "test2@example.com"
+ name2 "testuser2"
+ password2 "password2"
+ ssh-key2 "asdf2"]
+ (binding [db/get-time (fn [] (java.sql.Timestamp. ms))]
+ ;;TODO: What should be done about the key-file?
+ (is (db/add-user email name password ssh-key))
+ (binding [db/get-time (fn [] (java.sql.Timestamp. (long 1)))]
+ ;;TODO: What should be done about the key-file?
+ (is (db/update-user name email2 name2 password2 ssh-key2))
+ (are [x] (submap {:email email2
+ :user name2
+ :ssh_key ssh-key2
+ :created ms}
+ x)
+ (db/find-user name2)
+ (db/find-user-by-user-or-email name2)
+ (db/find-user-by-user-or-email email2)
+ (db/auth-user name2 password2)
+ (db/auth-user email2 password2)))
+ (is (not (db/find-user name))))))
+
+(deftest added-users-are-added-only-to-their-org-clojars-group
+ (let [email "test@example.com"
+ name "testuser"
+ password "password"
+ ssh-key "asdf"]
+ ;;TODO: What should be done about the key-file?
+ (is (db/add-user email name password ssh-key))
+ (is (= ["testuser"]
+ (db/group-members (str "org.clojars." name))))
+ (is (= ["org.clojars.testuser"]
+ (db/find-groups name)))))
+
+(deftest users-can-be-added-to-groups
+ (let [email "test@example.com"
+ name "testuser"
+ password "password"
+ ssh-key "asdf"]
+ ;;TODO: What should be done about the key-file?
+ (is (db/add-user email name password ssh-key))
+ (is (db/add-member "test-group" name))
+ (is (= ["testuser"]
+ (db/group-members "test-group")))
+ (is (some #{"test-group"}
+ (db/find-groups name)))))
+
+;;TODO: Tests below should have the users added first.
+;;Currently user unenforced foreign keys are by name
+;;so these are faking relationships
+
+(deftest added-jars-can-be-found
+ (let [name "tester"
+ ms (long 0)
+ jarmap {:name name :group name :version "1.0"
+ :description "An dog awesome and non-existent test jar."
+ :homepage "http://clojars.org/"
+ :authors ["Alex Osborne" "a little fish"]}
+ result {:jar_name name
+ :version "1.0"
+ :homepage "http://clojars.org/"
+ :scm nil
+ :user "test-user"
+ :created ms
+ :group_name name
+ :authors "Alex Osborne, a little fish"
+ :description "An dog awesome and non-existent test jar."}]
+ (binding [db/get-time (fn [] (java.sql.Timestamp. ms))]
+ (is (db/add-jar "test-user" jarmap))
+ (are [x] (submap result x)
+ (db/find-canon-jar name)
+ (db/find-jar name)
+ (db/find-jar name name)
+ (first (db/jars-by-group name))
+ (first (db/jars-by-user "test-user"))))))
+
+(deftest jars-by-group-only-returns-most-recent-version
+ (let [name "tester"
+ jarmap {:name name :group name :version "1" }
+ result {:jar_name name
+ :version "2"
+ :user "test-user"
+ :group_name name }]
+ (binding [db/get-time (fn [] (java.sql.Timestamp. 0))]
+ (is (db/add-jar "test-user" jarmap))
+ (binding [db/get-time (fn [] (java.sql.Timestamp. 1))]
+ (is (db/add-jar "test-user" (assoc jarmap :version "2")))))
+ (let [jars (db/jars-by-group name)]
+ (dorun (map #(is (submap %1 %2)) [result] jars))
+ (is (= 1 (count jars))))))
+
+(deftest jars-by-group-returns-all-jars-in-group
+ (let [name "tester"
+ jarmap {:name name :group name :version "1" }
+ result {:jar_name name
+ :version "1"
+ :group_name name }]
+ (db/add-member name "test-user")
+ (db/add-member "tester-group" "test-user2")
+ (db/add-member name "test-user2")
+ (binding [db/get-time (fn [] (java.sql.Timestamp. 0))]
+ (is (db/add-jar "test-user" jarmap)))
+ (binding [db/get-time (fn [] (java.sql.Timestamp. 1))]
+ (is (db/add-jar "test-user" (assoc jarmap :name "tester2"))))
+ (binding [db/get-time (fn [] (java.sql.Timestamp. 2))]
+ (is (db/add-jar "test-user2" (assoc jarmap :name "tester3"))))
+ (binding [db/get-time (fn [] (java.sql.Timestamp. 3))]
+ (is (db/add-jar "test-user2" (assoc jarmap :group "tester-group"))))
+ (let [jars (db/jars-by-group name)]
+ (dorun (map #(is (submap %1 %2))
+ [result
+ (assoc result :jar_name "tester2")
+ (assoc result :jar_name "tester3")]
+ jars))
+ (is (= 3 (count jars))))))
+
+(deftest jars-by-user-only-returns-most-recent-version
+ (let [name "tester"
+ jarmap {:name name :group name :version "1" }
+ result {:jar_name name
+ :version "2"
+ :user "test-user"
+ :group_name name }]
+ (binding [db/get-time (fn [] (java.sql.Timestamp. 0))]
+ (is (db/add-jar "test-user" jarmap))
+ (binding [db/get-time (fn [] (java.sql.Timestamp. 1))]
+ (is (db/add-jar "test-user" (assoc jarmap :version "2")))))
+ (let [jars (db/jars-by-user "test-user")]
+ (dorun (map #(is (submap %1 %2)) [result] jars))
+ (is (= 1 (count jars))))))
+
+(deftest jars-by-user-returns-all-jars-by-user
+ (let [name "tester"
+ jarmap {:name name :group name :version "1" }
+ result {:jar_name name
+ :user "test-user"
+ :version "1"
+ :group_name name }]
+ (db/add-member name "test-user")
+ (db/add-member "tester-group" "test-user")
+ (db/add-member name "test-user2")
+ (binding [db/get-time (fn [] (java.sql.Timestamp. 0))]
+ (is (db/add-jar "test-user" jarmap)))
+ (binding [db/get-time (fn [] (java.sql.Timestamp. 1))]
+ (is (db/add-jar "test-user" (assoc jarmap :name "tester2"))))
+ (binding [db/get-time (fn [] (java.sql.Timestamp. 2))]
+ (is (db/add-jar "test-user2" (assoc jarmap :name "tester3"))))
+ (binding [db/get-time (fn [] (java.sql.Timestamp. 3))]
+ (is (db/add-jar "test-user" (assoc jarmap :group "tester-group"))))
+ (let [jars (db/jars-by-user "test-user")]
+ (dorun (map #(is (submap %1 %2))
+ [result
+ (assoc result :jar_name "tester2")
+ (assoc result :group_name "tester-group")]
+ jars))
+ (is (= 3 (count jars))))))
+
+(deftest add-jar-validates-jar-name-format
+ (let [jarmap {:group "group-name" :version "1"}]
+ (is (thrown? Exception (db/add-jar "test-user" jarmap)))
+ (is (thrown? Exception (db/add-jar "test-user"
+ (assoc jarmap :name "HI"))))
+ (is (thrown? Exception (db/add-jar "test-user"
+ (assoc jarmap :name "lein*"))))
+ (is (thrown? Exception (db/add-jar "test-user"
+ (assoc jarmap :name "lein="))))
+ (is (thrown? Exception (db/add-jar "test-user"
+ (assoc jarmap :name "lein>"))))
+ (is (thrown? Exception (db/add-jar "test-user"
+ (assoc jarmap :name ""))))
+ (is (db/add-jar "test-user" (assoc jarmap :name "hi")))
+ (is (db/add-jar "test-user" (assoc jarmap :name "hi-")))
+ (is (db/add-jar "test-user" (assoc jarmap :name "hi_1...2")))))
+
+(deftest add-jar-validates-group-name-format
+ (let [jarmap {:name "jar-name" :version "1"}]
+ (is (thrown? Exception (db/add-jar "test-user" jarmap)))
+ (is (thrown? Exception (db/add-jar "test-user"
+ (assoc jarmap :group "HI"))))
+ (is (thrown? Exception (db/add-jar "test-user"
+ (assoc jarmap :group "lein*"))))
+ (is (thrown? Exception (db/add-jar "test-user"
+ (assoc jarmap :group "lein="))))
+ (is (thrown? Exception (db/add-jar "test-user"
+ (assoc jarmap :group "lein>"))))
+ (is (thrown? Exception (db/add-jar "test-user"
+ (assoc jarmap :group ""))))
+ (is (db/add-jar "test-user" (assoc jarmap :group "hi")))
+ (is (db/add-jar "test-user" (assoc jarmap :group "hi-")))
+ (is (db/add-jar "test-user" (assoc jarmap :group "hi_1...2")))))
+
+(deftest add-jar-validates-group-name-is-not-reserved
+ (let [jarmap {:name "jar-name" :version "1"}]
+ (doseq [group db/reserved-names]
+ (is (thrown? Exception (db/add-jar "test-user"
+ (assoc jarmap :group group)))))))
+
+(deftest add-jar-validates-group-permissions
+ (let [jarmap {:name "jar-name" :version "1" :group "group-name"}]
+ (db/add-member "group-name" "some-user")
+ (is (thrown? Exception (db/add-jar "test-user" jarmap)))))
+
+
+(deftest add-jar-creates-single-member-group-for-user
+ (let [jarmap {:name "jar-name" :version "1" :group "group-name"}]
+ (is (empty? (db/group-members "group-name")))
+ (db/add-jar "test-user" jarmap)
+ (is (= ["test-user"] (db/group-members "group-name")))
+ (is (= ["group-name"]
+ (db/find-groups "test-user")))))
+
+(deftest recent-jars-returns-5-most-recent-jars-only-most-recent-version
+ (let [name "tester"
+ ms (long 0)
+ jarmap {:name name :group name
+ :description "An dog awesome and non-existent test jar."
+ :homepage "http://clojars.org/"
+ :authors ["Alex Osborne" "a little fish"]
+ :version "1"}
+ result {:user "test-user"
+ :jar_name name
+ :version "1"
+ :homepage "http://clojars.org/"
+ :scm nil
+ :group_name name
+ :authors "Alex Osborne, a little fish"
+ :description "An dog awesome and non-existent test jar."}]
+ (binding [db/get-time (fn [] (java.sql.Timestamp. (long 1)))]
+ (db/add-jar "test-user" (assoc jarmap :name "1")))
+ (binding [db/get-time (fn [] (java.sql.Timestamp. (long 2)))]
+ (db/add-jar "test-user" (assoc jarmap :name "2")))
+ (binding [db/get-time (fn [] (java.sql.Timestamp. (long 3)))]
+ (db/add-jar "test-user" (assoc jarmap :name "3")))
+ (binding [db/get-time (fn [] (java.sql.Timestamp. (long 4)))]
+ (db/add-jar "test-user" (assoc jarmap :name "4")))
+ (binding [db/get-time (fn [] (java.sql.Timestamp. (long 5)))]
+ (db/add-jar "test-user" (assoc jarmap :version "5")))
+ (binding [db/get-time (fn [] (java.sql.Timestamp. (long 6)))]
+ (db/add-jar "test-user" (assoc jarmap :name "6")))
+ (binding [db/get-time (fn [] (java.sql.Timestamp. (long 7)))]
+ (db/add-jar "test-user" (assoc jarmap :version "7"))
+ (db/add-jar "test-user" (assoc jarmap :version "8")))
+ (dorun (map #(is (submap %1 %2))
+ [(assoc result :version "8")
+ (assoc result :jar_name "6")
+ (assoc result :jar_name "4")
+ (assoc result :jar_name "3")
+ (assoc result :jar_name "2")]
+ (db/recent-jars)))))
+
+;; TODO: search tests?
13 test/clojars/test/unit/web/common.clj
View
@@ -0,0 +1,13 @@
+(ns clojars.test.unit.web.common
+ (:use clojure.test)
+ (:require [clojars.web.common :as common]))
+
+;;TODO: more helper tests
+
+(deftest jar-name-uses-shortest-unique-and-html-escape
+ (is (= "group/artifact" (common/jar-name {:jar_name "artifact"
+ :group_name "group"})))
+ (is (= "artifact" (common/jar-name {:jar_name "artifact"
+ :group_name "artifact"})))
+ (is (= "&lt;/alert&gt;/&lt;alert&gt;" (common/jar-name {:jar_name "<alert>"
+ :group_name "</alert>"}))))
Please sign in to comment.
Something went wrong with that request. Please try again.