From eae64cbbe14efcfb6bb933148d992525a92f212c Mon Sep 17 00:00:00 2001 From: Christian Mueller Date: Sun, 5 Jan 2014 15:21:12 +0100 Subject: [PATCH 01/14] API tests implemented --- API.md | 8 +-- src/NoteHub/api.clj | 11 ++++ test/NoteHub/test/api.clj | 62 +++++++++++++++++++++ test/NoteHub/test/storage.clj | 102 +++++++++++++++++----------------- 4 files changed, 128 insertions(+), 55 deletions(-) create mode 100644 src/NoteHub/api.clj create mode 100644 test/NoteHub/test/api.clj diff --git a/API.md b/API.md index cd37b7b..41e1b2a 100644 --- a/API.md +++ b/API.md @@ -14,7 +14,7 @@ All API requests must be issued with one special parameter `version` denoting th A simple `GET` request to the following URL: - http://notehub.org/api/get-note?version=1.0&title= + http://notehub.org/api/note?version=1.0&title= will return a JSON object containing following self explaining fields: `note`, `longURL`, `shortURL`, `statistics`, `status`. @@ -42,7 +42,7 @@ The note ID is a string, containing the date of publishing and a few first words A note must be created by a `POST` request to the following URL: - http://notehub.org/api/post-note + http://notehub.org/api/note with the following parameters: @@ -74,9 +74,9 @@ The status object serves the same purpose as in the case of note retrieval. ## Note Update -To update a note, an `UPDATE` request must be issued to the following URL: +To update a note, an `PUT` request must be issued to the following URL: - http://notehub.org/api/update-note + http://notehub.org/api/note with the following parameters: diff --git a/src/NoteHub/api.clj b/src/NoteHub/api.clj new file mode 100644 index 0000000..159e6ca --- /dev/null +++ b/src/NoteHub/api.clj @@ -0,0 +1,11 @@ +(ns NoteHub.api) + +(def api-version "1.0") + +(defn get-signature [& args]) +(defn post-note [& args]) +(defn get-note [& args]) +(defn update-note [& args]) +(defn register-publisher [& args]) +(defn revoke-publisher [& args]) +(defn valid-publisher? [& args]) diff --git a/test/NoteHub/test/api.clj b/test/NoteHub/test/api.clj new file mode 100644 index 0000000..18946b4 --- /dev/null +++ b/test/NoteHub/test/api.clj @@ -0,0 +1,62 @@ +(ns NoteHub.test.api + (:use [NoteHub.api] [clojure.test])) + +(def note "Hello world, this is a test note!") +(def note2 "Another test note") +(def pid "somePlugin") +(def pid2 "somePlugin2") +(def ver api-version) + +(defmacro isnt [arg] `(is (not ~arg))) + +(defn register-publisher-fixture [f] + (def psk (register-publisher pid)) + (f) + (revoke-publisher pid)) + +(deftest api + (testing "API" + (testing "signature implementation" + (is (= 3577853521 (get-signature "Lorem ipsum dolor sit amet" "abcdef"))) + (is (= -180217198 (get-signature "Notehub is a free pastebin for markdown" "12345678"))) + (is (= 6887137804 (get-signature "abcd !§$%& параграф" "A VERY LONG KEY")))) + (testing "publisher registration" + (let [psk2 (register-publisher pid2)] + (is (valid-publisher? pid)) + (is (valid-publisher? pid2)) + (is (revoke-publisher pid2)) + (isnt (revoke-publisher "anyPID")) + (isnt (valid-publisher? "any_PID")) + (isnt (valid-publisher? pid2)))) + (testing "note publishing & retrieval" + (let [post-response (post-note note pid (get-signature note psk) ver) + get-response (get-note ver (:noteID post-response))] + (is (:success (:status post-response))) + (is (:success (:status get-response))) + (is (= note (:note get-response))) + (is (= (:longURL post-response) (:longURL get-response))) + (is (= (:shortURL post-response) (:shortURL get-response)))) + (isnt (:success (:status (post-note note pid (get-signature note2 psk) ver)))) + (isnt (:success (:status (post-note note pid (get-signature note "random_psk") ver)))) + (is (:success (:status (post-note note pid (get-signature note psk) ver)))) + (let [psk2 (register-publisher "randomPID")] + (is (:success (:status (post-note note "randomPID" (get-signature note psk2) ver)))) + (is (revoke-publisher pid2)) + (isnt (:success (:status (post-note note "randomPID" (get-signature note psk2) ver)))))) + (testing "note update" + (let [post-response (post-note note pid (get-signature note psk) ver "passwd") + note-id (:noteID post-response) + get-response (get-note ver note-id) + new-note "a new note!" + update-response (update-note note-id new-note pid (get-signature new-note psk) ver "passwd") + get-response-new (get-note ver note-id) + update-response-false (update-note note-id new-note pid (get-signature new-note psk) ver "pass") + ] + (is (:success (:status post-response))) + (is (:success (:status get-response))) + (is (:success (:status get-response-new))) + (is (:success (:status update-response))) + (isnt (:success (:status update-response-false))) + (is (= note (:note get-response))) + (is (= new-note (:note get-response-new))) + (is (= new-note (:note (get-note note-id)))))))) diff --git a/test/NoteHub/test/storage.clj b/test/NoteHub/test/storage.clj index cee6434..d8544ce 100644 --- a/test/NoteHub/test/storage.clj +++ b/test/NoteHub/test/storage.clj @@ -13,54 +13,54 @@ (deftest storage - (testing "Storage" - (testing "of short-url mechanism" - (let [url (create-short-url metadata) - url2 (create-short-url metadata)] - (is (short-url-exists? url)) - (is (= url url2)) - (is (= metadata (resolve-url url))) - (is (not (do - (delete-short-url url) - (short-url-exists? url)))))) - (testing "of correct note creation" - (is (= (do - (set-note date test-title test-note) - (get-note date test-title)) - test-note)) - (is (= "1" (get-note-views date test-title))) - (is (= (do - (get-note date test-title) - (get-note-views date test-title)) - "2"))) - (testing "of note update" - (is (= (do - (set-note date test-title test-note "12345qwert") - (get-note date test-title)) - test-note)) - (is (= (do - (update-note (build-key date test-title) "update" "12345qwert") - (get-note date test-title)) - "update")) - (is (= (do - (update-note (build-key date test-title) "not authorized" "44444") - (get-note date test-title)) - "update"))) - (testing "of the note access" - (is (not= (get-note date test-title) "any text"))) - (testing "session management" - (let [s1 (create-session) - s2 (create-session) - s3 (create-session)] - (is (invalidate-session s1)) - (is (not (invalidate-session (str s1 s2)))) - (is (invalidate-session s2)) - (is (not (invalidate-session "wrongtoken"))) - (is (invalidate-session s3)))) - (testing "of note existence" - (is (note-exists? date test-title)) - (is (not (do - (delete-note date test-title) - (note-exists? date test-title)))) - (is (not (note-exists? [2013 06 03] test-title))) - (is (not (note-exists? date "some title")))))) + (testing "Storage" + (testing "of short-url mechanism" + (let [url (create-short-url metadata) + url2 (create-short-url metadata)] + (is (short-url-exists? url)) + (is (= url url2)) + (is (= metadata (resolve-url url))) + (is (not (do + (delete-short-url url) + (short-url-exists? url)))))) + (testing "of correct note creation" + (is (= (do + (set-note date test-title test-note) + (get-note date test-title)) + test-note)) + (is (= "1" (get-note-views date test-title))) + (is (= (do + (get-note date test-title) + (get-note-views date test-title)) + "2"))) + (testing "of note update" + (is (= (do + (set-note date test-title test-note "12345qwert") + (get-note date test-title)) + test-note)) + (is (= (do + (update-note (build-key date test-title) "update" "12345qwert") + (get-note date test-title)) + "update")) + (is (= (do + (update-note (build-key date test-title) "not authorized" "44444") + (get-note date test-title)) + "update"))) + (testing "of the note access" + (is (not= (get-note date test-title) "any text"))) + (testing "session management" + (let [s1 (create-session) + s2 (create-session) + s3 (create-session)] + (is (invalidate-session s1)) + (is (not (invalidate-session (str s1 s2)))) + (is (invalidate-session s2)) + (is (not (invalidate-session "wrongtoken"))) + (is (invalidate-session s3)))) + (testing "of note existence" + (is (note-exists? date test-title)) + (is (not (do + (delete-note date test-title) + (note-exists? date test-title)))) + (is (not (note-exists? [2013 06 03] test-title))) + (is (not (note-exists? date "some title")))))) From 8cdbb01227c9d2bacb9b16d1136c503704890b2d Mon Sep 17 00:00:00 2001 From: Christian Mueller Date: Sun, 5 Jan 2014 18:26:33 +0100 Subject: [PATCH 02/14] API improved --- API.md | 37 +++++++++---------------------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/API.md b/API.md index 41e1b2a..d0718d3 100644 --- a/API.md +++ b/API.md @@ -54,7 +54,11 @@ Parameter | Explanation | Type `password` | MD5 hash of a plain password for editing | *optional* `version` | Used API version | **required** -The Signature must be computed on the client side using the note text _and_ the PSK, wrt. the algorithm described below. The signature serves as a proof, that the request is authentic and will be issued by the publisher corresponding to the provided PID. +The Signature is the MD5 hash of the following string concatenation: + + pid + psk + note + +The signature serves as a proof, that the request is authentic and will be issued by the publisher corresponding to the provided PID. The response of the server will contain the fields `noteID`, `longURL`, `shortURL`, `status`. @@ -89,7 +93,10 @@ Parameter | Explanation | Type `password` | MD5 hash of the plain password used for creation | **required** `version` | Used API version | **required** -The Signature is computed identically to the note creation. +The Signature is the MD5 hash of the following string concatenation: + + pid + psk + noteID + note + password + The response of the server will contain the fields `longURL`, `shortURL`, `status`. @@ -105,29 +112,3 @@ Example: } The status object serves the same purpose as in the case of note retrieval and creation. - -## Signature Implementation - -The signature is computed as a very simple hash function. Consider the following sample implementation in JavaScript: - - function getSignature(text, psk) { - var hash = 5381; - for (var pos = 0; pos < text.length; pos++){ - hash = ((hash << 5) + hash) + - (text.charCodeAt(pos) ^ psk.charCodeAt(hash % psk.length)); - } - return hash; - } - -Note: - - the `hash` variable is typed as a signed 32-bit integer - - `^` denotes an XOR - - `charCodeAt()` returns the char code of the note letter at the given position; e.g., it will return values [97, 167, 1092] for three characters of the string `a§ф`. - -Your can test your implementation on the following tests: - -Input | Output ---- | --- -"Lorem ipsum dolor sit amet", "abcdef" | 3577853521 -"Notehub is a free pastebin for markdown", "12345678" | -180217198 -"abcd !§$%& параграф", "A VERY LONG KEY" | 6887137804 From 932d2dcabc3c07170d304a956b39079f6f9620b9 Mon Sep 17 00:00:00 2001 From: Christian Mueller Date: Sun, 5 Jan 2014 18:31:06 +0100 Subject: [PATCH 03/14] API tests updated --- src/NoteHub/storage.clj | 6 ++++-- test/NoteHub/test/api.clj | 22 +++++++++------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/NoteHub/storage.clj b/src/NoteHub/storage.clj index 587eb38..7748742 100644 --- a/src/NoteHub/storage.clj +++ b/src/NoteHub/storage.clj @@ -28,7 +28,8 @@ "Creates a random session token" [] (let [token (encrypt (str (rand-int Integer/MAX_VALUE)))] - (do (redis/sadd db sessions token) token))) + (do (redis/sadd db sessions token) + token))) (defn invalidate-session "Invalidates given session" @@ -36,7 +37,8 @@ ; Jedis is buggy & returns an NPE for token == nil (when token (let [was-valid (redis/sismember db sessions token)] - (do (redis/srem db sessions token) was-valid)))) + (do (redis/srem db sessions token) + was-valid)))) (defn update-note "Updates a note with the given store key if the specified password is correct" diff --git a/test/NoteHub/test/api.clj b/test/NoteHub/test/api.clj index 18946b4..a57a22f 100644 --- a/test/NoteHub/test/api.clj +++ b/test/NoteHub/test/api.clj @@ -16,10 +16,6 @@ (deftest api (testing "API" - (testing "signature implementation" - (is (= 3577853521 (get-signature "Lorem ipsum dolor sit amet" "abcdef"))) - (is (= -180217198 (get-signature "Notehub is a free pastebin for markdown" "12345678"))) - (is (= 6887137804 (get-signature "abcd !§$%& параграф" "A VERY LONG KEY")))) (testing "publisher registration" (let [psk2 (register-publisher pid2)] (is (valid-publisher? pid)) @@ -29,28 +25,28 @@ (isnt (valid-publisher? "any_PID")) (isnt (valid-publisher? pid2)))) (testing "note publishing & retrieval" - (let [post-response (post-note note pid (get-signature note psk) ver) + (let [post-response (post-note note pid (get-signature pid psk note) ver) get-response (get-note ver (:noteID post-response))] (is (:success (:status post-response))) (is (:success (:status get-response))) (is (= note (:note get-response))) (is (= (:longURL post-response) (:longURL get-response))) (is (= (:shortURL post-response) (:shortURL get-response)))) - (isnt (:success (:status (post-note note pid (get-signature note2 psk) ver)))) - (isnt (:success (:status (post-note note pid (get-signature note "random_psk") ver)))) - (is (:success (:status (post-note note pid (get-signature note psk) ver)))) + (isnt (:success (:status (post-note note pid (get-signature pid psk note) ver)))) + (isnt (:success (:status (post-note note pid (get-signature pid "random_psk" note) ver)))) + (is (:success (:status (post-note note pid (get-signature pid psk note) ver)))) (let [psk2 (register-publisher "randomPID")] - (is (:success (:status (post-note note "randomPID" (get-signature note psk2) ver)))) + (is (:success (:status (post-note note "randomPID" (get-signature pid psk2 note) ver)))) (is (revoke-publisher pid2)) - (isnt (:success (:status (post-note note "randomPID" (get-signature note psk2) ver)))))) + (isnt (:success (:status (post-note note "randomPID" (get-signature pid psk2 note) ver)))))) (testing "note update" - (let [post-response (post-note note pid (get-signature note psk) ver "passwd") + (let [post-response (post-note note pid (get-signature pid psk note) ver "passwd") note-id (:noteID post-response) get-response (get-note ver note-id) new-note "a new note!" - update-response (update-note note-id new-note pid (get-signature new-note psk) ver "passwd") + update-response (update-note note-id new-note pid (get-signature pid psk new-note) ver "passwd") get-response-new (get-note ver note-id) - update-response-false (update-note note-id new-note pid (get-signature new-note psk) ver "pass") + update-response-false (update-note note-id new-note pid (get-signature pid psk new-note) ver "pass") ] (is (:success (:status post-response))) (is (:success (:status get-response))) From 5d73e315e9282e13b33ae2c1eccced554d595bd6 Mon Sep 17 00:00:00 2001 From: Christian Mueller Date: Sun, 5 Jan 2014 19:38:25 +0100 Subject: [PATCH 04/14] get-signature implemented --- src/NoteHub/api.clj | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/NoteHub/api.clj b/src/NoteHub/api.clj index 159e6ca..f9be74c 100644 --- a/src/NoteHub/api.clj +++ b/src/NoteHub/api.clj @@ -1,10 +1,24 @@ -(ns NoteHub.api) +(ns NoteHub.api + (:require [NoteHub.storage :as persistance])) (def api-version "1.0") -(defn get-signature [& args]) +(defn- create-response + ([success] { :success success }) + ([success message] + (assoc (create-response success) :message message))) + +(let [md5Instance (java.security.MessageDigest/getInstance "MD5")] + (defn get-signature + "Returns the MD5 hash for the concatenation of all passed parameters" + [& args] + (let [input (apply str args)] + (do (.reset md5Instance) + (.update md5Instance (.getBytes input)) + (.toString (new java.math.BigInteger 1 (.digest md5Instance)) 16))))) + +(defn get-note [noteID]) (defn post-note [& args]) -(defn get-note [& args]) (defn update-note [& args]) (defn register-publisher [& args]) (defn revoke-publisher [& args]) From f56e920ec89eda525a6bad0d36baed6fd71d3e09 Mon Sep 17 00:00:00 2001 From: Christian Mueller Date: Sun, 5 Jan 2014 19:46:11 +0100 Subject: [PATCH 05/14] api tests disabled --- test/NoteHub/test/api.clj | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/test/NoteHub/test/api.clj b/test/NoteHub/test/api.clj index a57a22f..1318250 100644 --- a/test/NoteHub/test/api.clj +++ b/test/NoteHub/test/api.clj @@ -5,7 +5,6 @@ (def note2 "Another test note") (def pid "somePlugin") (def pid2 "somePlugin2") -(def ver api-version) (defmacro isnt [arg] `(is (not ~arg))) @@ -14,6 +13,7 @@ (f) (revoke-publisher pid)) +#_ (deftest api (testing "API" (testing "publisher registration" @@ -25,28 +25,28 @@ (isnt (valid-publisher? "any_PID")) (isnt (valid-publisher? pid2)))) (testing "note publishing & retrieval" - (let [post-response (post-note note pid (get-signature pid psk note) ver) - get-response (get-note ver (:noteID post-response))] + (let [post-response (post-note note pid (get-signature pid psk note)) + get-response (get-note (:noteID post-response))] (is (:success (:status post-response))) (is (:success (:status get-response))) (is (= note (:note get-response))) (is (= (:longURL post-response) (:longURL get-response))) (is (= (:shortURL post-response) (:shortURL get-response)))) - (isnt (:success (:status (post-note note pid (get-signature pid psk note) ver)))) - (isnt (:success (:status (post-note note pid (get-signature pid "random_psk" note) ver)))) - (is (:success (:status (post-note note pid (get-signature pid psk note) ver)))) + (isnt (:success (:status (post-note note pid (get-signature pid psk note))))) + (isnt (:success (:status (post-note note pid (get-signature pid "random_psk" note))))) + (is (:success (:status (post-note note pid (get-signature pid psk note))))) (let [psk2 (register-publisher "randomPID")] - (is (:success (:status (post-note note "randomPID" (get-signature pid psk2 note) ver)))) + (is (:success (:status (post-note note "randomPID" (get-signature pid psk2 note))))) (is (revoke-publisher pid2)) - (isnt (:success (:status (post-note note "randomPID" (get-signature pid psk2 note) ver)))))) + (isnt (:success (:status (post-note note "randomPID" (get-signature pid psk2 note))))))) (testing "note update" - (let [post-response (post-note note pid (get-signature pid psk note) ver "passwd") + (let [post-response (post-note note pid (get-signature pid psk note) "passwd") note-id (:noteID post-response) - get-response (get-note ver note-id) + get-response (get-note note-id) new-note "a new note!" - update-response (update-note note-id new-note pid (get-signature pid psk new-note) ver "passwd") - get-response-new (get-note ver note-id) - update-response-false (update-note note-id new-note pid (get-signature pid psk new-note) ver "pass") + update-response (update-note note-id new-note pid (get-signature pid psk new-note) "passwd") + get-response-new (get-note note-id) + update-response-false (update-note note-id new-note pid (get-signature pid psk new-note) "pass") ] (is (:success (:status post-response))) (is (:success (:status get-response))) From dfb3a1c9344a1a1393a273c6d5a10bf582a67982 Mon Sep 17 00:00:00 2001 From: Christian Mueller Date: Sun, 5 Jan 2014 20:20:18 +0100 Subject: [PATCH 06/14] storage layer made more BL independent --- src/NoteHub/storage.clj | 66 ++++++++++++------------------- src/NoteHub/views/pages.clj | 21 ++++++---- test/NoteHub/test/storage.clj | 34 ++++++++-------- test/NoteHub/test/views/pages.clj | 23 ++++++----- 4 files changed, 69 insertions(+), 75 deletions(-) diff --git a/src/NoteHub/storage.clj b/src/NoteHub/storage.clj index 7748742..b76c02b 100644 --- a/src/NoteHub/storage.clj +++ b/src/NoteHub/storage.clj @@ -18,21 +18,13 @@ (def sessions "sessions") (def short-url "short-url") -; Concatenates all fields to a string -(defn build-key - "Returns a storage-key for the given note coordinates" - [[year month day] title] - (print-str year month day title)) - (defn create-session - "Creates a random session token" [] (let [token (encrypt (str (rand-int Integer/MAX_VALUE)))] (do (redis/sadd db sessions token) token))) (defn invalidate-session - "Invalidates given session" [token] ; Jedis is buggy & returns an NPE for token == nil (when token @@ -41,49 +33,43 @@ was-valid)))) (defn update-note - "Updates a note with the given store key if the specified password is correct" - [key text passwd] - (let [stored-password (redis/hget db password key)] + [noteID text passwd] + (let [stored-password (redis/hget db password noteID)] (when (and stored-password (= passwd stored-password)) - (redis/hset db note key text)))) + (redis/hset db note noteID text)))) -(defn set-note - "Creates a note with the given title and text in the given date namespace" - ([date title text] (set-note date title text nil)) - ([date title text passwd] - (let [key (build-key date title)] - (do - (redis/hset db note key text) - (when (not (blank? passwd)) - (redis/hset db password key passwd)))))) +(defn add-note + ([noteID text] (add-note noteID text nil)) + ([noteID text passwd] + (do + (redis/hset db note noteID text) + (when (not (blank? passwd)) + (redis/hset db password noteID passwd))))) (defn get-note - "Gets the note from the given date namespaces for the specified title" - [date title] - (let [key (build-key date title) - text (redis/hget db note key)] + [noteID] + (let [text (redis/hget db note noteID)] (when text (do - (redis/hincrby db views key 1) + (redis/hincrby db views noteID 1) text)))) (defn get-note-views - "Returns the number of views for the specified date and note title" - [date title] - (redis/hget db views (build-key date title))) + "Returns the number of views for the specified noteID" + [noteID] + (redis/hget db views noteID)) (defn note-exists? - "Returns true if the note with the specified title and date exists" - [date title] - (redis/hexists db note (build-key date title))) + "Returns true if the note with the specified noteID" + [noteID] + (redis/hexists db note noteID)) (defn delete-note "Deletes the note with the specified coordinates" - [date title] - (let [key (build-key date title)] - (doseq [kw [password views note]] - ; TODO: delete short url by looking for the title - (redis/hdel db kw key)))) + [noteID] + (doseq [kw [password views note]] + ; TODO: delete short url by looking for the title + (redis/hdel db kw noteID))) (defn short-url-exists? "Checks whether the provided short url is taken (for testing only)" @@ -99,10 +85,10 @@ (defn delete-short-url "Deletes a short url (for testing only)" - [key] - (let [value (redis/hget db short-url key)] + [noteID] + (let [value (redis/hget db short-url noteID)] (do - (redis/hdel db short-url key) + (redis/hdel db short-url noteID) (redis/hdel db short-url value)))) (defn create-short-url diff --git a/src/NoteHub/views/pages.clj b/src/NoteHub/views/pages.clj index 653a815..148c1f9 100644 --- a/src/NoteHub/views/pages.clj +++ b/src/NoteHub/views/pages.clj @@ -16,6 +16,12 @@ (:import [java.util Calendar])) +; Concatenates all fields to a string +(defn build-key + "Returns a storage-key for the given note coordinates" + [[year month day] title] + (print-str year month day title)) + (defn get-hash "A simple hash-function, which computes a hash from the text field content and given session number. It is intended to be used as a spam @@ -103,8 +109,9 @@ ; Update Note Page (defpage "/:year/:month/:day/:title/edit" {:keys [year month day title]} (input-form "/update-note" :update - (html (hidden-field :key (build-key [year month day] title))) - (get-note [year month day] title) :enter-passwd)) + (let [noteID (build-key [year month day] title)] + (html (hidden-field :key noteID)) + (get-note noteID) :enter-passwd))) ; New Note Page (defpage "/new" {} @@ -118,16 +125,16 @@ (wrap (create-short-url params) (select-keys params [:title :theme :header-font :text-font]) - (get-note [year month day] title))) + (get-note (build-key [year month day] title)))) ; Provides Markdown of the specified note (defpage "/:year/:month/:day/:title/export" {:keys [year month day title]} - (when-let [md-text (get-note [year month day] title)] + (when-let [md-text (get-note (build-key [year month day] title))] (content-type "text/plain; charset=utf-8" md-text))) ; Provides the number of views of the specified note (defpage "/:year/:month/:day/:title/stats" {:keys [year month day title]} - (when-let [views (get-note-views [year month day] title)] + (when-let [views (get-note-views (build-key [year month day] title))] (layout (get-message :statistics) [:table#stats.helvetica.central-element [:tr @@ -167,11 +174,11 @@ ; TODO: replace to ccs/take when it gets fixed proposed-title (apply str (take max-length title-uncut)) date [year month day] - title (first (drop-while #(note-exists? date %) + title (first (drop-while #(note-exists? (build-key date %)) (cons proposed-title (map #(str proposed-title "-" (+ 2 %)) (range)))))] (do - (set-note date title draft password) + (add-note (build-key date title) draft password) (redirect (url year month day title)))) (response 400)))) diff --git a/test/NoteHub/test/storage.clj b/test/NoteHub/test/storage.clj index d8544ce..adc4eb1 100644 --- a/test/NoteHub/test/storage.clj +++ b/test/NoteHub/test/storage.clj @@ -1,5 +1,7 @@ (ns NoteHub.test.storage - (:use [NoteHub.storage] [clojure.test])) + (:use [NoteHub.storage] + [NoteHub.views.pages] + [clojure.test])) (def date [2012 06 03]) (def test-title "Some title.") @@ -25,29 +27,29 @@ (short-url-exists? url)))))) (testing "of correct note creation" (is (= (do - (set-note date test-title test-note) - (get-note date test-title)) + (add-note (build-key date test-title) test-note) + (get-note (build-key date test-title))) test-note)) - (is (= "1" (get-note-views date test-title))) + (is (= "1" (get-note-views (build-key date test-title)))) (is (= (do - (get-note date test-title) - (get-note-views date test-title)) + (get-note (build-key date test-title)) + (get-note-views (build-key date test-title))) "2"))) (testing "of note update" (is (= (do - (set-note date test-title test-note "12345qwert") - (get-note date test-title)) + (add-note (build-key date test-title) test-note "12345qwert") + (get-note (build-key date test-title))) test-note)) (is (= (do (update-note (build-key date test-title) "update" "12345qwert") - (get-note date test-title)) + (get-note (build-key date test-title))) "update")) (is (= (do (update-note (build-key date test-title) "not authorized" "44444") - (get-note date test-title)) + (get-note (build-key date test-title))) "update"))) (testing "of the note access" - (is (not= (get-note date test-title) "any text"))) + (is (not= (get-note (build-key date test-title)) "any text"))) (testing "session management" (let [s1 (create-session) s2 (create-session) @@ -58,9 +60,9 @@ (is (not (invalidate-session "wrongtoken"))) (is (invalidate-session s3)))) (testing "of note existence" - (is (note-exists? date test-title)) + (is (note-exists? (build-key date test-title))) (is (not (do - (delete-note date test-title) - (note-exists? date test-title)))) - (is (not (note-exists? [2013 06 03] test-title))) - (is (not (note-exists? date "some title")))))) + (delete-note (build-key date test-title)) + (note-exists? (build-key date test-title))))) + (is (not (note-exists? (build-key [2013 06 03] test-title)))) + (is (not (note-exists? (build-key date "some title"))))))) diff --git a/test/NoteHub/test/views/pages.clj b/test/NoteHub/test/views/pages.clj index be36b2b..a4416b2 100644 --- a/test/NoteHub/test/views/pages.clj +++ b/test/NoteHub/test/views/pages.clj @@ -6,16 +6,15 @@ [clojure.test])) (defn substring? [a b] - (not (= nil - (re-matches (re-pattern (str "(?s).*" a ".*")) b)))) + (not (= nil (re-matches (re-pattern (str "(?s).*" a ".*")) b)))) (def date [2012 6 3]) (def test-title "some-title") (def test-note "# This is a test note.\nHello _world_. Motörhead, тест.") (defn create-testnote-fixture [f] - (set-note date test-title test-note) + (add-note (build-key date test-title) test-note) (f) - (delete-note date test-title)) + (delete-note (build-key date test-title))) (use-fixtures :each create-testnote-fixture) @@ -23,8 +22,8 @@ (deftest testing-fixture (testing "Was a not created?" - (is (= (get-note date test-title) test-note)) - (is (note-exists? date test-title)))) + (is (= (get-note (build-key date test-title)) test-note)) + (is (note-exists? (build-key date test-title))))) (deftest export-test (testing "Markdown export" @@ -42,12 +41,12 @@ {:session-key session-key :draft test-note :session-value (str (get-hash (str test-note session-key)))}) 302)) - (is (note-exists? date title)) + (is (note-exists? (build-key date title))) (is (substring? "Hello _world_" ((send-request (url year month day title)) :body))) (is (do - (delete-note date title) - (not (note-exists? date title))))))) + (delete-note (build-key date title)) + (not (note-exists? (build-key date title)))))))) (deftest note-update (let [session-key (create-session) @@ -62,7 +61,7 @@ :draft "test note" :password "qwerty" :session-value (str (get-hash (str "test note" session-key)))}) 302)) - (is (note-exists? date title)) + (is (note-exists? (build-key date title))) (is (substring? "test note" ((send-request (url year month day title)) :body))) (is (has-status @@ -82,8 +81,8 @@ (is (substring? "UPDATED CONTENT" ((send-request (url year month day title)) :body))) (is (do - (delete-note date title) - (not (note-exists? date title))))))) + (delete-note (build-key date title)) + (not (note-exists? (build-key date title)))))))) (deftest requests (testing "HTTP Status" From f39ae0a5938f025738feb03acb73ad1ba49b0ccd Mon Sep 17 00:00:00 2001 From: Christian Mueller Date: Sun, 5 Jan 2014 22:34:42 +0100 Subject: [PATCH 07/14] first api version implemented --- API.md | 5 +- messages | 1 + src/NoteHub/api.clj | 97 ++++++++++++++++++++++++++++--- src/NoteHub/storage.clj | 53 +++++++++++++---- src/NoteHub/views/pages.clj | 19 +----- test/NoteHub/test/api.clj | 33 ++++++----- test/NoteHub/test/storage.clj | 1 + test/NoteHub/test/views/pages.clj | 1 + 8 files changed, 157 insertions(+), 53 deletions(-) diff --git a/API.md b/API.md index d0718d3..4a0d9ba 100644 --- a/API.md +++ b/API.md @@ -4,7 +4,7 @@ ## Prerequisites -The NoteHub API can only be used in combination with a __Publisher ID__ (PID) and __Publisher Secret Key__ (PSK), which can be issued [here](http://notehub.org/api/register). The PSK can be revoked at any moment in case of an API abuse. +The NoteHub API can only be used in combination with a __Publisher ID__ (PID) and __Publisher Secret Key__ (PSK), which can be requested [here](http://notehub.org/api/register). The PSK can be revoked at any moment in case of an API abuse. A PID is a string chosen by the publisher and cannot be longer than 16 characters (e.g.: __notepadPlugin__). A PSK will be generated by the NoteHub API and can be a string of any length and content. @@ -26,6 +26,7 @@ Example: shortURL: "http://notehub.org/0vrcp", statistics: { published: "2014-1-3", + edited: "2014-1-12", views: 24 }, status: { @@ -36,7 +37,7 @@ Example: Hence, the status of the request can be evaluated by reading of the property `status.success`. The field `status.comment`might contain an error message, a warning or any other comments from the server. -The note ID is a string, containing the date of publishing and a few first words of the note (usually the header), e.g.: `2014/1/3/lorem-ipsum`. This ID will be generated by NoteHub automatically. +The note ID is a string, containing the date of publishing and a few first words of the note (usually the title), e.g.: `2014 1 3 lorem-ipsum`. This ID will be generated by NoteHub automatically. ## Note Creation diff --git a/messages b/messages index c60d2e2..2773864 100644 --- a/messages +++ b/messages @@ -16,6 +16,7 @@ enter-passwd = Password publish = Publish update = Save published = Published +edited = Edited article-views = Article Views statistics = Statistics stats = statistics diff --git a/src/NoteHub/api.clj b/src/NoteHub/api.clj index f9be74c..66644ef 100644 --- a/src/NoteHub/api.clj +++ b/src/NoteHub/api.clj @@ -1,12 +1,37 @@ (ns NoteHub.api - (:require [NoteHub.storage :as persistance])) + (:import + [java.util Calendar]) + (:use + [NoteHub.settings] + [clojure.string :rename {replace sreplace} + :only [replace blank? lower-case split-lines]]) + (:require [NoteHub.storage :as storage])) (def api-version "1.0") +(def domain "http://notehub.org/") + +; Concatenates all fields to a string +(defn build-key + "Returns a storage-key for the given note coordinates" + [[year month day] title] + (print-str year month day title)) + +(defn get-date + "Returns today's date" + [] + (map #(+ (second %) (.get (Calendar/getInstance) (first %))) + {Calendar/YEAR 0, Calendar/MONTH 1, Calendar/DAY_OF_MONTH 0})) + (defn- create-response ([success] { :success success }) - ([success message] - (assoc (create-response success) :message message))) + ([success message & params] + (assoc (create-response success) :message (apply format message params)))) + +(defn- getURL [noteID description] + (if (description) + (str domain (storage/get-short-url noteID)) + (str domain (sreplace noteID #" " "/")))) (let [md5Instance (java.security.MessageDigest/getInstance "MD5")] (defn get-signature @@ -17,9 +42,63 @@ (.update md5Instance (.getBytes input)) (.toString (new java.math.BigInteger 1 (.digest md5Instance)) 16))))) -(defn get-note [noteID]) -(defn post-note [& args]) -(defn update-note [& args]) -(defn register-publisher [& args]) -(defn revoke-publisher [& args]) -(defn valid-publisher? [& args]) +(defn get-note [noteID] + {:note (storage/get-note noteID) + :longURL (getURL noteID) + :shortURL (getURL noteID :short) + :statistics (storage/get-note-statistics noteID) + :status (if (storage/note-exists? noteID) + (create-response true) + (create-response false "noteID '%s' unknown" noteID))}) + +(defn post-note + ([note pid signature] (post-note note pid signature nil)) + ([note pid signature password] + (let [errors (filter identity + (lazy-seq + [(when-not (storage/valid-publisher? pid) "pid invalid") + (when-not (= signature + (get-signature pid (storage/get-psk pid) note)) + "signature invalid") + (when (blank? note) "note is empty")]))] + (if (empty? errors) + (let [[year month day] (get-date) + untrimmed-line (filter #(or (= \- %) (Character/isLetterOrDigit %)) + (-> note split-lines first (sreplace " " "-") lower-case)) + trim (fn [s] (apply str (drop-while #(= \- %) s))) + title-uncut (-> untrimmed-line trim reverse trim reverse) + max-length (get-setting :max-title-length #(Integer/parseInt %) 80) + proposed-title (apply str (take max-length title-uncut)) + date [year month day] + title (first (drop-while #(storage/note-exists? (build-key date %)) + (cons proposed-title + (map #(str proposed-title "-" (+ 2 %)) (range))))) + noteID (build-key date title)] + (do + (storage/add-note noteID note password) + (storage/create-short-url noteID) + { + :noteID noteID + :longURL (getURL noteID) + :shortURL (getURL noteID :short) + :status (create-response true) + })) + {:status (create-response false (first errors))})))) + + +(defn update-note [noteID note pid signature password] + (let [errors (filter identity + (lazy-seq + [(when-not (storage/valid-publisher? pid) "pid invalid") + (when-not (= signature + (get-signature pid (storage/get-psk pid) noteID note password)) + "signature invalid") + (when (blank? note) "note is empty") + (when-not (storage/update-note noteID note password) "password invalid")]))] + (if (empty? errors) + { + :longURL (getURL noteID) + :shortURL (getURL noteID :short) + :status (create-response true) + } + {:status (create-response false (first errors))}))) diff --git a/src/NoteHub/storage.clj b/src/NoteHub/storage.clj index b76c02b..d1c5dab 100644 --- a/src/NoteHub/storage.clj +++ b/src/NoteHub/storage.clj @@ -11,12 +11,34 @@ (when-not (dev-mode?) {:url (get-setting :db-url)}))) +(defn get-current-date [] + (str (java.util.Date.))) + ; DB hierarchy levels (def note "note") +(def published "published") +(def edited "edited") (def views "views") (def password "password") (def sessions "sessions") (def short-url "short-url") +(def publisher "publisher") + +(defn valid-publisher? [pid] + (redis/hexists db publisher pid)) + +(defn register-publisher [pid] + "Returns nil if given PID exists or a PSK otherwise" + (when (not (valid-publisher? pid)) + (let [psk (encrypt (str (rand-int Integer/MAX_VALUE) pid)) + _ (redis/hset db publisher pid psk)] + psk))) + +(defn revoke-publisher [pid] + (redis/hdel db publisher pid)) + +(defn get-psk [pid] + (redis/hget db publisher pid)) (defn create-session [] @@ -36,6 +58,7 @@ [noteID text passwd] (let [stored-password (redis/hget db password noteID)] (when (and stored-password (= passwd stored-password)) + (redis/hset db edited noteID (get-current-date)) (redis/hset db note noteID text)))) (defn add-note @@ -43,6 +66,7 @@ ([noteID text passwd] (do (redis/hset db note noteID text) + (redis/hset db published noteID (get-current-date)) (when (not (blank? passwd)) (redis/hset db password noteID passwd))))) @@ -55,17 +79,21 @@ text)))) (defn get-note-views - "Returns the number of views for the specified noteID" [noteID] (redis/hget db views noteID)) +(defn get-note-statistics + "Return views, publishing and editing timestamp" + [noteID] + { :view (redis/hget db views noteID) + :published (redis/hget db published noteID) + :edited (redis/hget db edited noteID) }) + (defn note-exists? - "Returns true if the note with the specified noteID" [noteID] (redis/hexists db note noteID)) (defn delete-note - "Deletes the note with the specified coordinates" [noteID] (doseq [kw [password views note]] ; TODO: delete short url by looking for the title @@ -76,6 +104,9 @@ [url] (redis/hexists db short-url url)) +(defn get-short-url [noteID] + (redis/hget db short-url noteID)) + (defn resolve-url "Resolves short url by providing all metadata of the request" [url] @@ -92,12 +123,12 @@ (redis/hdel db short-url value)))) (defn create-short-url - "Creates a short url for the given request metadata or extracts + "Creates a short url for the given request metadata or noteID or extracts one if it was already created" - [metadata] - (let [request (str (into (sorted-map) metadata))] - (if (short-url-exists? request) - (redis/hget db short-url request) + [arg] + (let [key (if (map? arg) (str (into (sorted-map) arg)) arg)] + (if (short-url-exists? key) + (redis/hget db short-url key) (let [hash-stream (partition 5 (repeatedly #(rand-int 36))) hash-to-string (fn [hash] (apply str @@ -108,8 +139,8 @@ (remove short-url-exists? (map hash-to-string hash-stream)))] (do - ; we create two mappings: request params -> short url and back, + ; we create two mappings: key params -> short url and back, ; s.t. we can later easily check whether a short url already exists - (redis/hset db short-url url request) - (redis/hset db short-url request url) + (redis/hset db short-url url key) + (redis/hset db short-url key url) url))))) diff --git a/src/NoteHub/views/pages.clj b/src/NoteHub/views/pages.clj index 148c1f9..4c41f04 100644 --- a/src/NoteHub/views/pages.clj +++ b/src/NoteHub/views/pages.clj @@ -1,7 +1,8 @@ (ns NoteHub.views.pages (:require [hiccup.util :as util]) (:use - [NoteHub.storage] + [NoteHub.storage] ; TODO: delete this + [NoteHub.api :only [build-key get-date]] [NoteHub.settings] [NoteHub.views.common] [clojure.string :rename {replace sreplace} @@ -12,15 +13,7 @@ [hiccup.element] [noir.response :only [redirect status content-type]] [noir.core :only [defpage defpartial]] - [noir.statuses]) - (:import - [java.util Calendar])) - -; Concatenates all fields to a string -(defn build-key - "Returns a storage-key for the given note coordinates" - [[year month day] title] - (print-str year month day title)) + [noir.statuses])) (defn get-hash "A simple hash-function, which computes a hash from the text field @@ -66,12 +59,6 @@ links (interpose separator links)] [:div#panel (map identity links)])))) -(defn get-date - "Returns today's date" - [] - (map #(+ (second %) (.get (Calendar/getInstance) (first %))) - {Calendar/YEAR 0, Calendar/MONTH 1, Calendar/DAY_OF_MONTH 0})) - ; Routes ; ====== diff --git a/test/NoteHub/test/api.clj b/test/NoteHub/test/api.clj index 1318250..4f2104a 100644 --- a/test/NoteHub/test/api.clj +++ b/test/NoteHub/test/api.clj @@ -1,5 +1,8 @@ (ns NoteHub.test.api - (:use [NoteHub.api] [clojure.test])) + (:require + [NoteHub.storage :as storage]) + (:use [NoteHub.api] + [clojure.test])) (def note "Hello world, this is a test note!") (def note2 "Another test note") @@ -9,37 +12,37 @@ (defmacro isnt [arg] `(is (not ~arg))) (defn register-publisher-fixture [f] - (def psk (register-publisher pid)) + (def psk (storage/register-publisher pid)) (f) - (revoke-publisher pid)) + (storage/revoke-publisher pid)) -#_ (deftest api (testing "API" (testing "publisher registration" - (let [psk2 (register-publisher pid2)] - (is (valid-publisher? pid)) - (is (valid-publisher? pid2)) - (is (revoke-publisher pid2)) - (isnt (revoke-publisher "anyPID")) - (isnt (valid-publisher? "any_PID")) - (isnt (valid-publisher? pid2)))) - (testing "note publishing & retrieval" + (let [psk2 (storage/register-publisher pid2)] + (is (storage/valid-publisher? pid)) + (is (storage/valid-publisher? pid2)) + (is (storage/revoke-publisher pid2)) + (isnt (storage/revoke-publisher "anyPID")) + (isnt (storage/valid-publisher? "any_PID")) + (isnt (storage/valid-publisher? pid2)))) + #_ (testing "note publishing & retrieval" (let [post-response (post-note note pid (get-signature pid psk note)) get-response (get-note (:noteID post-response))] (is (:success (:status post-response))) (is (:success (:status get-response))) (is (= note (:note get-response))) + ; TODO: test all response fields!!!! (is (= (:longURL post-response) (:longURL get-response))) (is (= (:shortURL post-response) (:shortURL get-response)))) (isnt (:success (:status (post-note note pid (get-signature pid psk note))))) (isnt (:success (:status (post-note note pid (get-signature pid "random_psk" note))))) (is (:success (:status (post-note note pid (get-signature pid psk note))))) - (let [psk2 (register-publisher "randomPID")] + (let [psk2 (storage/register-publisher "randomPID")] (is (:success (:status (post-note note "randomPID" (get-signature pid psk2 note))))) - (is (revoke-publisher pid2)) + (is (storage/revoke-publisher pid2)) (isnt (:success (:status (post-note note "randomPID" (get-signature pid psk2 note))))))) - (testing "note update" + #_ (testing "note update" (let [post-response (post-note note pid (get-signature pid psk note) "passwd") note-id (:noteID post-response) get-response (get-note note-id) diff --git a/test/NoteHub/test/storage.clj b/test/NoteHub/test/storage.clj index adc4eb1..8c92178 100644 --- a/test/NoteHub/test/storage.clj +++ b/test/NoteHub/test/storage.clj @@ -1,5 +1,6 @@ (ns NoteHub.test.storage (:use [NoteHub.storage] + [NoteHub.api :only [build-key]] [NoteHub.views.pages] [clojure.test])) diff --git a/test/NoteHub/test/views/pages.clj b/test/NoteHub/test/views/pages.clj index a4416b2..b8c38b3 100644 --- a/test/NoteHub/test/views/pages.clj +++ b/test/NoteHub/test/views/pages.clj @@ -1,5 +1,6 @@ (ns NoteHub.test.views.pages (:use [NoteHub.views.pages] + [NoteHub.api :only [build-key get-date]] [noir.util.test] [NoteHub.views.common :only [url]] [NoteHub.storage] From 5e6e7fb5c752960b5bfa568e076bd61a53aa9ad7 Mon Sep 17 00:00:00 2001 From: Christian Mueller Date: Sun, 5 Jan 2014 22:46:42 +0100 Subject: [PATCH 08/14] improving tests; wip --- src/NoteHub/api.clj | 4 ++-- test/NoteHub/test/api.clj | 46 ++++++++++++++++++++------------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/NoteHub/api.clj b/src/NoteHub/api.clj index 66644ef..f77c0bd 100644 --- a/src/NoteHub/api.clj +++ b/src/NoteHub/api.clj @@ -28,8 +28,8 @@ ([success message & params] (assoc (create-response success) :message (apply format message params)))) -(defn- getURL [noteID description] - (if (description) +(defn- getURL [noteID & description] + (if description (str domain (storage/get-short-url noteID)) (str domain (sreplace noteID #" " "/")))) diff --git a/test/NoteHub/test/api.clj b/test/NoteHub/test/api.clj index 4f2104a..cbdcf0b 100644 --- a/test/NoteHub/test/api.clj +++ b/test/NoteHub/test/api.clj @@ -1,11 +1,9 @@ (ns NoteHub.test.api (:require - [NoteHub.storage :as storage]) + [NoteHub.storage :as storage]) (:use [NoteHub.api] [clojure.test])) -(def note "Hello world, this is a test note!") -(def note2 "Another test note") (def pid "somePlugin") (def pid2 "somePlugin2") @@ -14,16 +12,20 @@ (defn register-publisher-fixture [f] (def psk (storage/register-publisher pid)) (f) - (storage/revoke-publisher pid)) + (storage/revoke-publisher pid) + (storage/revoke-publisher pid2)) + +(use-fixtures :each register-publisher-fixture) (deftest api (testing "API" (testing "publisher registration" + (is (storage/valid-publisher? pid)) + (isnt (storage/valid-publisher? pid2)) (let [psk2 (storage/register-publisher pid2)] - (is (storage/valid-publisher? pid)) + (is (= psk2 (storage/get-psk pid2))) (is (storage/valid-publisher? pid2)) (is (storage/revoke-publisher pid2)) - (isnt (storage/revoke-publisher "anyPID")) (isnt (storage/valid-publisher? "any_PID")) (isnt (storage/valid-publisher? pid2)))) #_ (testing "note publishing & retrieval" @@ -43,19 +45,19 @@ (is (storage/revoke-publisher pid2)) (isnt (:success (:status (post-note note "randomPID" (get-signature pid psk2 note))))))) #_ (testing "note update" - (let [post-response (post-note note pid (get-signature pid psk note) "passwd") - note-id (:noteID post-response) - get-response (get-note note-id) - new-note "a new note!" - update-response (update-note note-id new-note pid (get-signature pid psk new-note) "passwd") - get-response-new (get-note note-id) - update-response-false (update-note note-id new-note pid (get-signature pid psk new-note) "pass") - ] - (is (:success (:status post-response))) - (is (:success (:status get-response))) - (is (:success (:status get-response-new))) - (is (:success (:status update-response))) - (isnt (:success (:status update-response-false))) - (is (= note (:note get-response))) - (is (= new-note (:note get-response-new))) - (is (= new-note (:note (get-note note-id)))))))) + (let [post-response (post-note note pid (get-signature pid psk note) "passwd") + note-id (:noteID post-response) + get-response (get-note note-id) + new-note "a new note!" + update-response (update-note note-id new-note pid (get-signature pid psk new-note) "passwd") + get-response-new (get-note note-id) + update-response-false (update-note note-id new-note pid (get-signature pid psk new-note) "pass") + ] + (is (:success (:status post-response))) + (is (:success (:status get-response))) + (is (:success (:status get-response-new))) + (is (:success (:status update-response))) + (isnt (:success (:status update-response-false))) + (is (= note (:note get-response))) + (is (= new-note (:note get-response-new))) + (is (= new-note (:note (get-note note-id)))))))) From bf7cebf04497c69ec1375d6640aa48926c0a078b Mon Sep 17 00:00:00 2001 From: Christian Mueller Date: Tue, 7 Jan 2014 23:02:09 +0100 Subject: [PATCH 09/14] tests fixed --- src/NoteHub/api.clj | 19 +++++----- src/NoteHub/storage.clj | 11 ++++++ test/NoteHub/test/api.clj | 71 ++++++++++++++++++++++------------- test/NoteHub/test/storage.clj | 1 + 4 files changed, 66 insertions(+), 36 deletions(-) diff --git a/src/NoteHub/api.clj b/src/NoteHub/api.clj index f77c0bd..747eb67 100644 --- a/src/NoteHub/api.clj +++ b/src/NoteHub/api.clj @@ -55,12 +55,11 @@ ([note pid signature] (post-note note pid signature nil)) ([note pid signature password] (let [errors (filter identity - (lazy-seq [(when-not (storage/valid-publisher? pid) "pid invalid") (when-not (= signature (get-signature pid (storage/get-psk pid) note)) "signature invalid") - (when (blank? note) "note is empty")]))] + (when (blank? note) "note is empty")])] (if (empty? errors) (let [[year month day] (get-date) untrimmed-line (filter #(or (= \- %) (Character/isLetterOrDigit %)) @@ -88,17 +87,19 @@ (defn update-note [noteID note pid signature password] (let [errors (filter identity - (lazy-seq + (seq [(when-not (storage/valid-publisher? pid) "pid invalid") (when-not (= signature (get-signature pid (storage/get-psk pid) noteID note password)) "signature invalid") (when (blank? note) "note is empty") - (when-not (storage/update-note noteID note password) "password invalid")]))] + (when-not (storage/valid-password? noteID password) "password invalid")]))] (if (empty? errors) - { - :longURL (getURL noteID) - :shortURL (getURL noteID :short) - :status (create-response true) - } + (do + (storage/edit-note noteID note) + { + :longURL (getURL noteID) + :shortURL (getURL noteID :short) + :status (create-response true) + }) {:status (create-response false (first errors))}))) diff --git a/src/NoteHub/storage.clj b/src/NoteHub/storage.clj index d1c5dab..59e7a50 100644 --- a/src/NoteHub/storage.clj +++ b/src/NoteHub/storage.clj @@ -54,6 +54,7 @@ (do (redis/srem db sessions token) was-valid)))) +; TODO: deprecated (defn update-note [noteID text passwd] (let [stored-password (redis/hget db password noteID)] @@ -61,6 +62,12 @@ (redis/hset db edited noteID (get-current-date)) (redis/hset db note noteID text)))) +(defn edit-note + [noteID text] + (do + (redis/hset db edited noteID (get-current-date)) + (redis/hset db note noteID text))) + (defn add-note ([noteID text] (add-note noteID text nil)) ([noteID text passwd] @@ -70,6 +77,10 @@ (when (not (blank? passwd)) (redis/hset db password noteID passwd))))) +(defn valid-password? [noteID passwd] + (let [stored (redis/hget db password noteID)] + (and stored (= stored passwd)))) + (defn get-note [noteID] (let [text (redis/hget db note noteID)] diff --git a/test/NoteHub/test/api.clj b/test/NoteHub/test/api.clj index cbdcf0b..1b2d4f3 100644 --- a/test/NoteHub/test/api.clj +++ b/test/NoteHub/test/api.clj @@ -4,8 +4,11 @@ (:use [NoteHub.api] [clojure.test])) +(def note "hello world! This is a _test_ note!") (def pid "somePlugin") (def pid2 "somePlugin2") +(def note-title (str (apply print-str (get-date)) " hello-world-this-is-a-test-note")) +(def note-url (str domain (apply str (interpose "/" (get-date))) "/hello-world-this-is-a-test-note")) (defmacro isnt [arg] `(is (not ~arg))) @@ -13,7 +16,8 @@ (def psk (storage/register-publisher pid)) (f) (storage/revoke-publisher pid) - (storage/revoke-publisher pid2)) + (storage/revoke-publisher pid2) + (storage/delete-note note-title)) (use-fixtures :each register-publisher-fixture) @@ -28,36 +32,49 @@ (is (storage/revoke-publisher pid2)) (isnt (storage/valid-publisher? "any_PID")) (isnt (storage/valid-publisher? pid2)))) - #_ (testing "note publishing & retrieval" + (testing "note publishing & retrieval" + (isnt (:success (:status (get-note "some note id")))) (let [post-response (post-note note pid (get-signature pid psk note)) get-response (get-note (:noteID post-response))] + (is (= "note is empty" (:message (:status (post-note "" pid (get-signature pid psk "")))))) (is (:success (:status post-response))) (is (:success (:status get-response))) (is (= note (:note get-response))) - ; TODO: test all response fields!!!! - (is (= (:longURL post-response) (:longURL get-response))) - (is (= (:shortURL post-response) (:shortURL get-response)))) - (isnt (:success (:status (post-note note pid (get-signature pid psk note))))) + (is (= (:longURL post-response) (:longURL get-response) note-url)) + (is (= (:shortURL post-response) (:shortURL get-response))))) + (testing "creation with wrong signature" + (let [response (post-note note pid (get-signature pid2 psk note))] + (isnt (:success (:status response))) + (is (= "signature invalid" (:message (:status response))))) + (let [response (post-note note pid (get-signature pid2 psk "any note"))] + (isnt (:success (:status response))) + (is (= "signature invalid" (:message (:status response))))) (isnt (:success (:status (post-note note pid (get-signature pid "random_psk" note))))) (is (:success (:status (post-note note pid (get-signature pid psk note))))) - (let [psk2 (storage/register-publisher "randomPID")] - (is (:success (:status (post-note note "randomPID" (get-signature pid psk2 note))))) - (is (storage/revoke-publisher pid2)) - (isnt (:success (:status (post-note note "randomPID" (get-signature pid psk2 note))))))) - #_ (testing "note update" - (let [post-response (post-note note pid (get-signature pid psk note) "passwd") - note-id (:noteID post-response) - get-response (get-note note-id) - new-note "a new note!" - update-response (update-note note-id new-note pid (get-signature pid psk new-note) "passwd") - get-response-new (get-note note-id) - update-response-false (update-note note-id new-note pid (get-signature pid psk new-note) "pass") - ] - (is (:success (:status post-response))) - (is (:success (:status get-response))) - (is (:success (:status get-response-new))) - (is (:success (:status update-response))) - (isnt (:success (:status update-response-false))) - (is (= note (:note get-response))) - (is (= new-note (:note get-response-new))) - (is (= new-note (:note (get-note note-id)))))))) + (let [randomPID "randomPID" + psk2 (storage/register-publisher randomPID) + _ (storage/revoke-publisher randomPID) + response (post-note note randomPID (get-signature randomPID psk2 note))] + (isnt (:success (:status response))) + (is (= (:message (:status response)) "pid invalid")))) + (testing "note update" + (let [post-response (post-note note pid (get-signature pid psk note) "passwd") + note-id (:noteID post-response) + new-note "a new note!"] + (is (:success (:status post-response))) + (is (:success (:status (get-note note-id)))) + (is (= note (:note (get-note note-id)))) + (let [update-response (update-note note-id new-note pid (get-signature pid psk new-note) "passwd")] + (isnt (:success (:status update-response))) + (is (= "signature invalid" (:message (:status update-response))))) + (is (= note (:note (get-note note-id)))) + (let [update-response (update-note note-id new-note pid + (get-signature pid psk note-id new-note "passwd") "passwd")] + (is (= { :success true } (:status update-response))) + (is (= new-note (:note (get-note note-id))))) + (let [update-response (update-note note-id "aaa" pid + (get-signature pid psk note-id "aaa" "pass") "pass")] + (isnt (:success (:status update-response))) + (is (= "password invalid" (:message (:status update-response))))) + (is (= new-note (:note (get-note note-id)))) + (is (= new-note (:note (get-note note-id)))))))) diff --git a/test/NoteHub/test/storage.clj b/test/NoteHub/test/storage.clj index 8c92178..d1e6957 100644 --- a/test/NoteHub/test/storage.clj +++ b/test/NoteHub/test/storage.clj @@ -41,6 +41,7 @@ (add-note (build-key date test-title) test-note "12345qwert") (get-note (build-key date test-title))) test-note)) + #_(is (valid-password? (build-key date test-title) "12345qwert")) (is (= (do (update-note (build-key date test-title) "update" "12345qwert") (get-note (build-key date test-title))) From 32312717446e646f28829fc34cfd7e90a1d96a0b Mon Sep 17 00:00:00 2001 From: Christian Mueller Date: Thu, 9 Jan 2014 20:37:23 +0100 Subject: [PATCH 10/14] tests extended --- .gitignore | 1 + src/NoteHub/storage.clj | 2 +- test/NoteHub/test/api.clj | 6 +++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index b7de877..9aa5ef1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ pom.xml .lein-* .crossover-cljs target/ +.nrepl-port diff --git a/src/NoteHub/storage.clj b/src/NoteHub/storage.clj index 59e7a50..9123662 100644 --- a/src/NoteHub/storage.clj +++ b/src/NoteHub/storage.clj @@ -96,7 +96,7 @@ (defn get-note-statistics "Return views, publishing and editing timestamp" [noteID] - { :view (redis/hget db views noteID) + { :views (redis/hget db views noteID) :published (redis/hget db published noteID) :edited (redis/hget db edited noteID) }) diff --git a/test/NoteHub/test/api.clj b/test/NoteHub/test/api.clj index 1b2d4f3..8f292a6 100644 --- a/test/NoteHub/test/api.clj +++ b/test/NoteHub/test/api.clj @@ -41,7 +41,10 @@ (is (:success (:status get-response))) (is (= note (:note get-response))) (is (= (:longURL post-response) (:longURL get-response) note-url)) - (is (= (:shortURL post-response) (:shortURL get-response))))) + (is (= (:shortURL post-response) (:shortURL get-response))) + (is (= "1" (get-in get-response [:statistics :views]))) + (isnt (get-in get-response [:statistics :edited])) + (is (= "2" (get-in (get-note (:noteID post-response)) [:statistics :views]))))) (testing "creation with wrong signature" (let [response (post-note note pid (get-signature pid2 psk note))] (isnt (:success (:status response))) @@ -71,6 +74,7 @@ (let [update-response (update-note note-id new-note pid (get-signature pid psk note-id new-note "passwd") "passwd")] (is (= { :success true } (:status update-response))) + (isnt (= nil (get-in (get-note note-id) [:statistics :edited]))) (is (= new-note (:note (get-note note-id))))) (let [update-response (update-note note-id "aaa" pid (get-signature pid psk note-id "aaa" "pass") "pass")] From 5f7d9ab251f176fa8e99be5b5b498c25b13da3bd Mon Sep 17 00:00:00 2001 From: Christian Mueller Date: Sun, 12 Jan 2014 11:25:56 +0100 Subject: [PATCH 11/14] api registration page added --- API.md | 9 ++++++--- LANDING.md | 2 ++ messages | 1 + project.clj | 1 + resources/public/js/main.js | 8 ++++++-- src/NoteHub/views/pages.clj | 25 +++++++++++++++---------- 6 files changed, 31 insertions(+), 15 deletions(-) diff --git a/API.md b/API.md index 4a0d9ba..6fb32e1 100644 --- a/API.md +++ b/API.md @@ -1,20 +1,22 @@ # NoteHub API -**Version 1.0, status: draft!** +**Version 1.0, status: released.** ## Prerequisites -The NoteHub API can only be used in combination with a __Publisher ID__ (PID) and __Publisher Secret Key__ (PSK), which can be requested [here](http://notehub.org/api/register). The PSK can be revoked at any moment in case of an API abuse. +The NoteHub API can only be used in combination with a __Publisher ID__ (PID) and __Publisher Secret Key__ (PSK), which can be requested [here](#registration). The PSK can be revoked at any moment in case of an API abuse. A PID is a string chosen by the publisher and cannot be longer than 16 characters (e.g.: __notepadPlugin__). A PSK will be generated by the NoteHub API and can be a string of any length and content. All API requests must be issued with one special parameter `version` denoting the expected version of the API as a string, e.g. `1.0` (see examples below). You should always put the version of this document as a `version` parameter. +## NoteHub API Access Request +To register as a publisher and gain access to NoteHub API, please [send](mailto:notehub@icloud.com?subject=NoteHub API Access Request&body=Please add [a] your contact information, [b] short usage explanation and [c] the URL of the resource or it's website.) an email with the following information about you: contact information, short description of what you want to do and an URL of the resource where the API will be used or its website. ## Note Retrieval A simple `GET` request to the following URL: - http://notehub.org/api/note?version=1.0&title= + http://notehub.org/api/note?version=1.0¬eID= will return a JSON object containing following self explaining fields: `note`, `longURL`, `shortURL`, `statistics`, `status`. @@ -113,3 +115,4 @@ Example: } The status object serves the same purpose as in the case of note retrieval and creation. + diff --git a/LANDING.md b/LANDING.md index bf38654..ba09b0f 100644 --- a/LANDING.md +++ b/LANDING.md @@ -1,5 +1,6 @@ ## News + - January 2014: NoteHub API v1.0 [introduced](/api). - January 2014: NoteHub 2.0 released: new theme, more performance, extended markdown - September 2013: Solarized color theme [added](https://github.com/chmllr/NoteHub/pull/4) (thanks Brandon!) ([Demo](http://notehub.org/2012/6/16/how-notehub-is-built?theme=solarized)) @@ -9,6 +10,7 @@ Send your feedback and comments directly to [@gravitydenier](http://twitter.com/gravitydenier) or open an [issue](https://github.com/chmllr/NoteHub/issues) on GitHub. ## Features +- **API**: Integrate the publishing functionality into your editor using the official [NoteHub API](/api). - **Color Themes**: specify the color scheme by appending the corresponding parameter to the URL: - [Dark](http://notehub.org/2012/6/16/how-notehub-is-built?theme=dark) - [Solarized-Dark](http://notehub.org/2012/6/16/how-notehub-is-built?theme=solarized-dark) diff --git a/messages b/messages index 2773864..5dfd746 100644 --- a/messages +++ b/messages @@ -23,3 +23,4 @@ stats = statistics export = export edit = edit short-url = short url +api-registration = NoteHub API Access Request diff --git a/project.clj b/project.clj index d630a7c..4425f98 100644 --- a/project.clj +++ b/project.clj @@ -2,6 +2,7 @@ :description "A free and anonymous hosting for markdown pages." :dependencies [[org.clojure/clojure "1.5.1"] [hiccup "1.0.0"] + [cheshire "5.3.1"] [ring/ring-core "1.1.0"] [clj-redis "0.0.12"] [noir "1.3.0-beta1"]] diff --git a/resources/public/js/main.js b/resources/public/js/main.js index ad7ae89..0047d25 100644 --- a/resources/public/js/main.js +++ b/resources/public/js/main.js @@ -16,6 +16,10 @@ var timerDelay = iosDetected ? 800 : 400; var show = function(elem) { elem.style.display = "block" } var $draft, $action, $preview, $password, $plain_password, $input_elems, $dashed_line, updatePreview; +function md2html(input){ + return marked(input); +} + function loadPage() { $draft = $("draft"); $action = $("action"); @@ -31,7 +35,7 @@ function loadPage() { timer = setTimeout(function(){ show($dashed_line); show($input_elems); - $preview.innerHTML = marked(content); + $preview.innerHTML = md2html(content); }, delay); }; if($action){ @@ -47,7 +51,7 @@ function loadPage() { var mdDocs = document.getElementsByClassName("markdown"); for(var i = 0; i < mdDocs.length; i++){ - mdDocs[i].innerHTML = marked(mdDocs[i].innerHTML); + mdDocs[i].innerHTML = md2html(mdDocs[i].innerHTML); show(mdDocs[i]); } } diff --git a/src/NoteHub/views/pages.clj b/src/NoteHub/views/pages.clj index 4c41f04..d54a98a 100644 --- a/src/NoteHub/views/pages.clj +++ b/src/NoteHub/views/pages.clj @@ -1,8 +1,9 @@ (ns NoteHub.views.pages - (:require [hiccup.util :as util]) + (:require [hiccup.util :as util] + [NoteHub.api :as api] + [cheshire.core :refer :all]) (:use [NoteHub.storage] ; TODO: delete this - [NoteHub.api :only [build-key get-date]] [NoteHub.settings] [NoteHub.views.common] [clojure.string :rename {replace sreplace} @@ -95,8 +96,8 @@ ; Update Note Page (defpage "/:year/:month/:day/:title/edit" {:keys [year month day title]} - (input-form "/update-note" :update - (let [noteID (build-key [year month day] title)] + (let [noteID (api/build-key [year month day] title)] + (input-form "/update-note" :update (html (hidden-field :key noteID)) (get-note noteID) :enter-passwd))) @@ -112,16 +113,16 @@ (wrap (create-short-url params) (select-keys params [:title :theme :header-font :text-font]) - (get-note (build-key [year month day] title)))) + (get-note (api/build-key [year month day] title)))) ; Provides Markdown of the specified note (defpage "/:year/:month/:day/:title/export" {:keys [year month day title]} - (when-let [md-text (get-note (build-key [year month day] title))] + (when-let [md-text (get-note (api/build-key [year month day] title))] (content-type "text/plain; charset=utf-8" md-text))) ; Provides the number of views of the specified note (defpage "/:year/:month/:day/:title/stats" {:keys [year month day title]} - (when-let [views (get-note-views (build-key [year month day] title))] + (when-let [views (get-note-views (api/build-key [year month day] title))] (layout (get-message :statistics) [:table#stats.helvetica.central-element [:tr @@ -152,7 +153,7 @@ ; if yes, we compute the current date, extract a title string from the text, ; which will be a part of the url and look whether this title is free today; ; if not, append "-n", where "n" is the next free number - (let [[year month day] (get-date) + (let [[year month day] (api/get-date) untrimmed-line (filter #(or (= \- %) (Character/isLetterOrDigit %)) (-> draft split-lines first (sreplace " " "-") lower-case)) trim (fn [s] (apply str (drop-while #(= \- %) s))) @@ -161,11 +162,11 @@ ; TODO: replace to ccs/take when it gets fixed proposed-title (apply str (take max-length title-uncut)) date [year month day] - title (first (drop-while #(note-exists? (build-key date %)) + title (first (drop-while #(note-exists? (api/build-key date %)) (cons proposed-title (map #(str proposed-title "-" (+ 2 %)) (range)))))] (do - (add-note (build-key date title) draft password) + (add-note (api/build-key date title) draft password) (redirect (url year month day title)))) (response 400)))) @@ -178,4 +179,8 @@ long-url (if (empty? rest-params) core-url (util/url core-url rest-params))] (redirect long-url)))) +; Here lives the API +(defpage "/api" args + (let [title (get-message :api-registration)] + (layout title [:article.markdown (slurp "API.md")]))) From 89031c2f0828f55bbd6b5da707d2b0c435941d7d Mon Sep 17 00:00:00 2001 From: Christian Mueller Date: Sun, 12 Jan 2014 11:59:23 +0100 Subject: [PATCH 12/14] added routing to the API --- API.md | 11 +++++++++-- src/NoteHub/views/pages.clj | 14 ++++++++++++++ test/NoteHub/test/storage.clj | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/API.md b/API.md index 6fb32e1..563d7d9 100644 --- a/API.md +++ b/API.md @@ -16,7 +16,14 @@ To register as a publisher and gain access to NoteHub API, please [send](mailto: A simple `GET` request to the following URL: - http://notehub.org/api/note?version=1.0¬eID= + http://notehub.org/api/note + +with the following parameters: + +Parameter | Explanation | Type +--- | --- | --- +`noteID` | Note-ID | **required** +`version` | Used API version | **required** will return a JSON object containing following self explaining fields: `note`, `longURL`, `shortURL`, `statistics`, `status`. @@ -39,7 +46,7 @@ Example: Hence, the status of the request can be evaluated by reading of the property `status.success`. The field `status.comment`might contain an error message, a warning or any other comments from the server. -The note ID is a string, containing the date of publishing and a few first words of the note (usually the title), e.g.: `2014 1 3 lorem-ipsum`. This ID will be generated by NoteHub automatically. +The note ID is a string, containing the date of publishing and a few first words of the note (usually the title), e.g.: `"2014 1 3 lorem-ipsum"`. This ID will be generated by NoteHub automatically. ## Note Creation diff --git a/src/NoteHub/views/pages.clj b/src/NoteHub/views/pages.clj index d54a98a..164630b 100644 --- a/src/NoteHub/views/pages.clj +++ b/src/NoteHub/views/pages.clj @@ -184,3 +184,17 @@ (defpage "/api" args (let [title (get-message :api-registration)] (layout title [:article.markdown (slurp "API.md")]))) + +(defpage [:get "/api/note"] {:keys [version noteID]} + (generate-string (api/get-note noteID))) + +(defpage [:post "/api/note"] {:keys [version note pid signature password]} + (generate-string (api/post-note note pid signature password))) + +(defpage [:put "/api/note"] {:keys [version noteID note pid signature password]} + (generate-string (api/update-note noteID note pid signature password))) + + + + + diff --git a/test/NoteHub/test/storage.clj b/test/NoteHub/test/storage.clj index d1e6957..68a0e34 100644 --- a/test/NoteHub/test/storage.clj +++ b/test/NoteHub/test/storage.clj @@ -41,7 +41,7 @@ (add-note (build-key date test-title) test-note "12345qwert") (get-note (build-key date test-title))) test-note)) - #_(is (valid-password? (build-key date test-title) "12345qwert")) + (is (valid-password? (build-key date test-title) "12345qwert")) (is (= (do (update-note (build-key date test-title) "update" "12345qwert") (get-note (build-key date test-title))) From 7796fd05db31c7643ca864ef0d005b02fd86e6db Mon Sep 17 00:00:00 2001 From: Christian Mueller Date: Sun, 12 Jan 2014 12:34:23 +0100 Subject: [PATCH 13/14] api-note-creation test added --- test/NoteHub/test/api.clj | 73 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/test/NoteHub/test/api.clj b/test/NoteHub/test/api.clj index 8f292a6..db04cbd 100644 --- a/test/NoteHub/test/api.clj +++ b/test/NoteHub/test/api.clj @@ -1,7 +1,9 @@ (ns NoteHub.test.api (:require + [cheshire.core :refer :all] [NoteHub.storage :as storage]) (:use [NoteHub.api] + [noir.util.test] [clojure.test])) (def note "hello world! This is a _test_ note!") @@ -82,3 +84,74 @@ (is (= "password invalid" (:message (:status update-response))))) (is (= new-note (:note (get-note note-id)))) (is (= new-note (:note (get-note note-id)))))))) + +(deftest api-note-creation + (testing "Note creation" + (let [response (send-request [:post "/api/note"] + {:note note + :pid pid + :signature (get-signature pid psk note) + :version "1.0"}) + body (parse-string (:body response))] + (is (has-status response 200)) + (is (get-in body ["status" "success"])) + (is (= note ((parse-string + (:body (send-request [:get "/api/note"] {:version "1.0" :noteID (body "noteID")}))) "note"))) + (is (do + (storage/delete-note (body "noteID")) + (not (storage/note-exists? (body "noteID")))))))) + +#_ +(deftest note-update + (let [session-key (create-session) + date (get-date) + title "test-note" + [year month day] date] + (testing "Note update" + (is (has-status + (send-request + [:post "/post-note"] + {:session-key session-key + :draft "test note" + :password "qwerty" + :session-value (str (get-hash (str "test note" session-key)))}) 302)) + (is (note-exists? (build-key date title))) + (is (substring? "test note" + ((send-request (url year month day title)) :body))) + (is (has-status + (send-request + [:post "/update-note"] + {:key (build-key [year month day] title) + :draft "WRONG pass" + :password "qwerty1" }) 403)) + (is (substring? "test note" + ((send-request (url year month day title)) :body))) + (is (has-status + (send-request + [:post "/update-note"] + {:key (build-key [year month day] title) + :draft "UPDATED CONTENT" + :password "qwerty" }) 302)) + (is (substring? "UPDATED CONTENT" + ((send-request (url year month day title)) :body))) + (is (do + (delete-note (build-key date title)) + (not (note-exists? (build-key date title)))))))) +#_ + +(deftest requests + (testing "HTTP Status" + (testing "of a wrong access" + (is (has-status (send-request "/wrong-page") 404)) + (is (has-status (send-request (url 2012 6 3 "lol" "stat")) 404)) + (is (has-status (send-request (url 2012 6 3 "lol" "export")) 404)) + (is (has-status (send-request (url 2012 6 3 "lol")) 404)) + (is (has-status (send-request (url 2012 6 4 "wrong-title")) 404))) + (testing "of corrupt note-post" + (is (has-status (send-request [:post "/post-note"]) 400))) + (testing "valid accesses" + ;(is (has-status (send-request "/new") 200) "accessing /new") + (is (has-status (send-request (url 2012 6 3 "some-title")) 200) "accessing test note") + (is (has-status (send-request (url 2012 6 3 "some-title" "export")) 200) "accessing test note's export") + (is (has-status (send-request (url 2012 6 3 "some-title" "stats")) 200) "accessing test note's stats") + (is (has-status (send-request "/") 200) "accessing landing page")))) From a5eaf38130298a9487ba972a5adb82ad75eb4049 Mon Sep 17 00:00:00 2001 From: Christian Mueller Date: Sun, 12 Jan 2014 14:38:19 +0100 Subject: [PATCH 14/14] routing tests added --- src/NoteHub/storage.clj | 2 +- test/NoteHub/test/api.clj | 104 ++++++++++++++---------------- test/NoteHub/test/views/pages.clj | 3 +- 3 files changed, 52 insertions(+), 57 deletions(-) diff --git a/src/NoteHub/storage.clj b/src/NoteHub/storage.clj index 9123662..70cd6eb 100644 --- a/src/NoteHub/storage.clj +++ b/src/NoteHub/storage.clj @@ -106,7 +106,7 @@ (defn delete-note [noteID] - (doseq [kw [password views note]] + (doseq [kw [password views note published edited]] ; TODO: delete short url by looking for the title (redis/hdel db kw noteID))) diff --git a/test/NoteHub/test/api.clj b/test/NoteHub/test/api.clj index db04cbd..c8ba67d 100644 --- a/test/NoteHub/test/api.clj +++ b/test/NoteHub/test/api.clj @@ -11,6 +11,7 @@ (def pid2 "somePlugin2") (def note-title (str (apply print-str (get-date)) " hello-world-this-is-a-test-note")) (def note-url (str domain (apply str (interpose "/" (get-date))) "/hello-world-this-is-a-test-note")) +(defn substring? [a b] (not (= nil (re-matches (re-pattern (str "(?s).*" a ".*")) b)))) (defmacro isnt [arg] `(is (not ~arg))) @@ -92,66 +93,61 @@ :pid pid :signature (get-signature pid psk note) :version "1.0"}) - body (parse-string (:body response))] + body (parse-string (:body response)) + noteID (body "noteID")] (is (has-status response 200)) (is (get-in body ["status" "success"])) (is (= note ((parse-string - (:body (send-request [:get "/api/note"] {:version "1.0" :noteID (body "noteID")}))) "note"))) + (:body (send-request [:get "/api/note"] {:version "1.0" :noteID noteID}))) "note"))) (is (do - (storage/delete-note (body "noteID")) - (not (storage/note-exists? (body "noteID")))))))) + (storage/delete-note noteID) + (not (storage/note-exists? noteID))))))) -#_ (deftest note-update - (let [session-key (create-session) - date (get-date) - title "test-note" - [year month day] date] + (let [response (send-request [:post "/api/note"] + {:note note + :pid pid + :signature (get-signature pid psk note) + :version "1.0" + :password "qwerty"}) + body (parse-string (:body response)) + noteID (body "noteID")] (testing "Note update" - (is (has-status - (send-request - [:post "/post-note"] - {:session-key session-key - :draft "test note" - :password "qwerty" - :session-value (str (get-hash (str "test note" session-key)))}) 302)) - (is (note-exists? (build-key date title))) - (is (substring? "test note" - ((send-request (url year month day title)) :body))) - (is (has-status - (send-request - [:post "/update-note"] - {:key (build-key [year month day] title) - :draft "WRONG pass" - :password "qwerty1" }) 403)) - (is (substring? "test note" - ((send-request (url year month day title)) :body))) - (is (has-status - (send-request - [:post "/update-note"] - {:key (build-key [year month day] title) - :draft "UPDATED CONTENT" - :password "qwerty" }) 302)) + (is (has-status response 200)) + (is (get-in body ["status" "success"])) + (is (storage/note-exists? noteID)) + (is (substring? "_test_ note" + ((parse-string + (:body (send-request [:get "/api/note"] {:version "1.0" :noteID noteID}))) "note"))) + (let [response (send-request [:put "/api/note"] + {:noteID noteID + :note "WRONG pass" + :pid pid + :signature (get-signature pid psk noteID "WRONG pass" "qwerty1") + :password "qwerty1" + :version "1.0"}) + body (parse-string (:body response))] + (is (has-status response 200)) + (isnt (get-in body ["status" "success"])) + (is (= "password invalid" (get-in body ["status" "message"]))) + (isnt (get-in body ["statistics" "edited"])) + (is (substring? "_test_ note" + ((parse-string + (:body (send-request [:get "/api/note"] {:version "1.0" :noteID noteID}))) "note")))) + (is (get-in (parse-string + (:body (send-request [:put "/api/note"] + {:noteID noteID + :note "UPDATED CONTENT" + :pid pid + :signature (get-signature pid psk noteID "UPDATED CONTENT" "qwerty") + :password "qwerty" + :version "1.0"}))) ["status" "success"])) + (isnt (= nil (((parse-string + (:body (send-request [:get "/api/note"] {:version "1.0" :noteID noteID}))) + "statistics") "edited"))) (is (substring? "UPDATED CONTENT" - ((send-request (url year month day title)) :body))) + ((parse-string + (:body (send-request [:get "/api/note"] {:version "1.0" :noteID noteID}))) "note"))) (is (do - (delete-note (build-key date title)) - (not (note-exists? (build-key date title)))))))) -#_ - -(deftest requests - (testing "HTTP Status" - (testing "of a wrong access" - (is (has-status (send-request "/wrong-page") 404)) - (is (has-status (send-request (url 2012 6 3 "lol" "stat")) 404)) - (is (has-status (send-request (url 2012 6 3 "lol" "export")) 404)) - (is (has-status (send-request (url 2012 6 3 "lol")) 404)) - (is (has-status (send-request (url 2012 6 4 "wrong-title")) 404))) - (testing "of corrupt note-post" - (is (has-status (send-request [:post "/post-note"]) 400))) - (testing "valid accesses" - ;(is (has-status (send-request "/new") 200) "accessing /new") - (is (has-status (send-request (url 2012 6 3 "some-title")) 200) "accessing test note") - (is (has-status (send-request (url 2012 6 3 "some-title" "export")) 200) "accessing test note's export") - (is (has-status (send-request (url 2012 6 3 "some-title" "stats")) 200) "accessing test note's stats") - (is (has-status (send-request "/") 200) "accessing landing page")))) + (storage/delete-note noteID) + (not (storage/note-exists? noteID))))))) diff --git a/test/NoteHub/test/views/pages.clj b/test/NoteHub/test/views/pages.clj index b8c38b3..1bebd7b 100644 --- a/test/NoteHub/test/views/pages.clj +++ b/test/NoteHub/test/views/pages.clj @@ -6,8 +6,7 @@ [NoteHub.storage] [clojure.test])) -(defn substring? [a b] - (not (= nil (re-matches (re-pattern (str "(?s).*" a ".*")) b)))) +(defn substring? [a b] (not (= nil (re-matches (re-pattern (str "(?s).*" a ".*")) b)))) (def date [2012 6 3]) (def test-title "some-title") (def test-note "# This is a test note.\nHello _world_. Motörhead, тест.")