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/API.md b/API.md
index d0718d3..2c02a12 100644
--- a/API.md
+++ b/API.md
@@ -1,20 +1,29 @@
# 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 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](#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
+
+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`.
@@ -26,6 +35,7 @@ Example:
shortURL: "http://notehub.org/0vrcp",
statistics: {
published: "2014-1-3",
+ edited: "2014-1-12",
views: 24
},
status: {
@@ -36,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 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/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 c60d2e2..5dfd746 100644
--- a/messages
+++ b/messages
@@ -16,9 +16,11 @@ enter-passwd = Password
publish = Publish
update = Save
published = Published
+edited = Edited
article-views = Article Views
statistics = Statistics
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/api.clj b/src/NoteHub/api.clj
new file mode 100644
index 0000000..747eb67
--- /dev/null
+++ b/src/NoteHub/api.clj
@@ -0,0 +1,105 @@
+(ns NoteHub.api
+ (: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 & 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
+ "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]
+ {: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
+ [(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
+ (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/valid-password? noteID password) "password invalid")]))]
+ (if (empty? errors)
+ (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 587eb38..70cd6eb 100644
--- a/src/NoteHub/storage.clj
+++ b/src/NoteHub/storage.clj
@@ -11,83 +11,113 @@
(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)))
-; 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 revoke-publisher [pid]
+ (redis/hdel db publisher pid))
+
+(defn get-psk [pid]
+ (redis/hget db publisher pid))
(defn create-session
- "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"
[token]
; 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))))
+; TODO: deprecated
(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))))
-
-(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))))))
+ (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]
+ (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)))))
+
+(defn valid-password? [noteID passwd]
+ (let [stored (redis/hget db password noteID)]
+ (and stored (= stored 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)))
+ [noteID]
+ (redis/hget db views noteID))
+
+(defn get-note-statistics
+ "Return views, publishing and editing timestamp"
+ [noteID]
+ { :views (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 title and date exists"
- [date title]
- (redis/hexists db note (build-key date title)))
+ [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 published edited]]
+ ; 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)"
[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]
@@ -97,19 +127,19 @@
(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
- "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
@@ -120,8 +150,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 653a815..164630b 100644
--- a/src/NoteHub/views/pages.clj
+++ b/src/NoteHub/views/pages.clj
@@ -1,7 +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]
+ [NoteHub.storage] ; TODO: delete this
[NoteHub.settings]
[NoteHub.views.common]
[clojure.string :rename {replace sreplace}
@@ -12,9 +14,7 @@
[hiccup.element]
[noir.response :only [redirect status content-type]]
[noir.core :only [defpage defpartial]]
- [noir.statuses])
- (:import
- [java.util Calendar]))
+ [noir.statuses]))
(defn get-hash
"A simple hash-function, which computes a hash from the text field
@@ -60,12 +60,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
; ======
@@ -102,9 +96,10 @@
; 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 (api/build-key [year month day] title)]
+ (input-form "/update-note" :update
+ (html (hidden-field :key noteID))
+ (get-note noteID) :enter-passwd)))
; New Note Page
(defpage "/new" {}
@@ -118,16 +113,16 @@
(wrap
(create-short-url params)
(select-keys params [:title :theme :header-font :text-font])
- (get-note [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 [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 [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
@@ -158,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)))
@@ -167,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? date %)
+ title (first (drop-while #(note-exists? (api/build-key date %))
(cons proposed-title
(map #(str proposed-title "-" (+ 2 %)) (range)))))]
(do
- (set-note date title draft password)
+ (add-note (api/build-key date title) draft password)
(redirect (url year month day title))))
(response 400))))
@@ -184,4 +179,22 @@
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")])))
+
+(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/api.clj b/test/NoteHub/test/api.clj
new file mode 100644
index 0000000..c8ba67d
--- /dev/null
+++ b/test/NoteHub/test/api.clj
@@ -0,0 +1,153 @@
+(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!")
+(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"))
+(defn substring? [a b] (not (= nil (re-matches (re-pattern (str "(?s).*" a ".*")) b))))
+
+(defmacro isnt [arg] `(is (not ~arg)))
+
+(defn register-publisher-fixture [f]
+ (def psk (storage/register-publisher pid))
+ (f)
+ (storage/revoke-publisher pid)
+ (storage/revoke-publisher pid2)
+ (storage/delete-note note-title))
+
+(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 (= psk2 (storage/get-psk pid2)))
+ (is (storage/valid-publisher? pid2))
+ (is (storage/revoke-publisher pid2))
+ (isnt (storage/valid-publisher? "any_PID"))
+ (isnt (storage/valid-publisher? pid2))))
+ (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)))
+ (is (= (:longURL post-response) (:longURL get-response) note-url))
+ (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)))
+ (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 [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)))
+ (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")]
+ (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))))))))
+
+(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))
+ 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 noteID}))) "note")))
+ (is (do
+ (storage/delete-note noteID)
+ (not (storage/note-exists? noteID)))))))
+
+(deftest note-update
+ (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 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"
+ ((parse-string
+ (:body (send-request [:get "/api/note"] {:version "1.0" :noteID noteID}))) "note")))
+ (is (do
+ (storage/delete-note noteID)
+ (not (storage/note-exists? noteID)))))))
diff --git a/test/NoteHub/test/storage.clj b/test/NoteHub/test/storage.clj
index cee6434..68a0e34 100644
--- a/test/NoteHub/test/storage.clj
+++ b/test/NoteHub/test/storage.clj
@@ -1,5 +1,8 @@
(ns NoteHub.test.storage
- (:use [NoteHub.storage] [clojure.test]))
+ (:use [NoteHub.storage]
+ [NoteHub.api :only [build-key]]
+ [NoteHub.views.pages]
+ [clojure.test]))
(def date [2012 06 03])
(def test-title "Some title.")
@@ -13,54 +16,55 @@
(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
+ (add-note (build-key date test-title) test-note)
+ (get-note (build-key date test-title)))
+ test-note))
+ (is (= "1" (get-note-views (build-key date test-title))))
+ (is (= (do
+ (get-note (build-key date test-title))
+ (get-note-views (build-key date test-title)))
+ "2")))
+ (testing "of note update"
+ (is (= (do
+ (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)))
+ "update"))
+ (is (= (do
+ (update-note (build-key date test-title) "not authorized" "44444")
+ (get-note (build-key date test-title)))
+ "update")))
+ (testing "of the note access"
+ (is (not= (get-note (build-key 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? (build-key date test-title)))
+ (is (not (do
+ (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..1bebd7b 100644
--- a/test/NoteHub/test/views/pages.clj
+++ b/test/NoteHub/test/views/pages.clj
@@ -1,21 +1,20 @@
(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]
[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, тест.")
(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"