Browse Source

merged with api branch

master
Christian Mueller 12 years ago
parent
commit
80a6b8fdc5
  1. 1
      .gitignore
  2. 18
      API.md
  3. 2
      LANDING.md
  4. 2
      messages
  5. 1
      project.clj
  6. 8
      resources/public/js/main.js
  7. 105
      src/NoteHub/api.clj
  8. 132
      src/NoteHub/storage.clj
  9. 53
      src/NoteHub/views/pages.clj
  10. 153
      test/NoteHub/test/api.clj
  11. 108
      test/NoteHub/test/storage.clj
  12. 25
      test/NoteHub/test/views/pages.clj

1
.gitignore vendored

@ -8,3 +8,4 @@ pom.xml
.lein-* .lein-*
.crossover-cljs .crossover-cljs
target/ target/
.nrepl-port

18
API.md

@ -1,20 +1,29 @@
# NoteHub API # NoteHub API
**Version 1.0, status: draft!** **Version 1.0, status: released.**
## Prerequisites ## 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. 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. 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.
## <a name="registration"></a>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 ## Note Retrieval
A simple `GET` request to the following URL: A simple `GET` request to the following URL:
http://notehub.org/api/note?version=1.0&title=<NOTE-ID> 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`. 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", shortURL: "http://notehub.org/0vrcp",
statistics: { statistics: {
published: "2014-1-3", published: "2014-1-3",
edited: "2014-1-12",
views: 24 views: 24
}, },
status: { 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. 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 ## Note Creation

2
LANDING.md

@ -1,5 +1,6 @@
## News ## News
- January 2014: NoteHub API v1.0 [introduced](/api).
- January 2014: NoteHub 2.0 released: new theme, more performance, extended markdown - 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)) - 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. 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 ## 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: - **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) - [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) - [Solarized-Dark](http://notehub.org/2012/6/16/how-notehub-is-built?theme=solarized-dark)

2
messages

@ -16,9 +16,11 @@ enter-passwd = Password
publish = Publish publish = Publish
update = Save update = Save
published = Published published = Published
edited = Edited
article-views = Article Views article-views = Article Views
statistics = Statistics statistics = Statistics
stats = statistics stats = statistics
export = export export = export
edit = edit edit = edit
short-url = short url short-url = short url
api-registration = NoteHub API Access Request

1
project.clj

@ -2,6 +2,7 @@
:description "A free and anonymous hosting for markdown pages." :description "A free and anonymous hosting for markdown pages."
:dependencies [[org.clojure/clojure "1.5.1"] :dependencies [[org.clojure/clojure "1.5.1"]
[hiccup "1.0.0"] [hiccup "1.0.0"]
[cheshire "5.3.1"]
[ring/ring-core "1.1.0"] [ring/ring-core "1.1.0"]
[clj-redis "0.0.12"] [clj-redis "0.0.12"]
[noir "1.3.0-beta1"]] [noir "1.3.0-beta1"]]

8
resources/public/js/main.js

@ -16,6 +16,10 @@ var timerDelay = iosDetected ? 800 : 400;
var show = function(elem) { elem.style.display = "block" } var show = function(elem) { elem.style.display = "block" }
var $draft, $action, $preview, $password, $plain_password, $input_elems, $dashed_line, updatePreview; var $draft, $action, $preview, $password, $plain_password, $input_elems, $dashed_line, updatePreview;
function md2html(input){
return marked(input);
}
function loadPage() { function loadPage() {
$draft = $("draft"); $draft = $("draft");
$action = $("action"); $action = $("action");
@ -31,7 +35,7 @@ function loadPage() {
timer = setTimeout(function(){ timer = setTimeout(function(){
show($dashed_line); show($dashed_line);
show($input_elems); show($input_elems);
$preview.innerHTML = marked(content); $preview.innerHTML = md2html(content);
}, delay); }, delay);
}; };
if($action){ if($action){
@ -47,7 +51,7 @@ function loadPage() {
var mdDocs = document.getElementsByClassName("markdown"); var mdDocs = document.getElementsByClassName("markdown");
for(var i = 0; i < mdDocs.length; i++){ 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]); show(mdDocs[i]);
} }
} }

105
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))})))

132
src/NoteHub/storage.clj

@ -11,83 +11,113 @@
(when-not (dev-mode?) (when-not (dev-mode?)
{:url (get-setting :db-url)}))) {:url (get-setting :db-url)})))
(defn get-current-date []
(str (java.util.Date.)))
; DB hierarchy levels ; DB hierarchy levels
(def note "note") (def note "note")
(def published "published")
(def edited "edited")
(def views "views") (def views "views")
(def password "password") (def password "password")
(def sessions "sessions") (def sessions "sessions")
(def short-url "short-url") (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 revoke-publisher [pid]
(defn build-key (redis/hdel db publisher pid))
"Returns a storage-key for the given note coordinates"
[[year month day] title] (defn get-psk [pid]
(print-str year month day title)) (redis/hget db publisher pid))
(defn create-session (defn create-session
"Creates a random session token"
[] []
(let [token (encrypt (str (rand-int Integer/MAX_VALUE)))] (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 (defn invalidate-session
"Invalidates given session"
[token] [token]
; Jedis is buggy & returns an NPE for token == nil ; Jedis is buggy & returns an NPE for token == nil
(when token (when token
(let [was-valid (redis/sismember db sessions 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 (defn update-note
"Updates a note with the given store key if the specified password is correct" [noteID text passwd]
[key text passwd] (let [stored-password (redis/hget db password noteID)]
(let [stored-password (redis/hget db password key)]
(when (and stored-password (= passwd stored-password)) (when (and stored-password (= passwd stored-password))
(redis/hset db note key text)))) (redis/hset db edited noteID (get-current-date))
(redis/hset db note noteID text))))
(defn set-note
"Creates a note with the given title and text in the given date namespace" (defn edit-note
([date title text] (set-note date title text nil)) [noteID text]
([date title text passwd] (do
(let [key (build-key date title)] (redis/hset db edited noteID (get-current-date))
(do (redis/hset db note noteID text)))
(redis/hset db note key text)
(when (not (blank? passwd)) (defn add-note
(redis/hset db password key passwd)))))) ([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 (defn get-note
"Gets the note from the given date namespaces for the specified title" [noteID]
[date title] (let [text (redis/hget db note noteID)]
(let [key (build-key date title)
text (redis/hget db note key)]
(when text (when text
(do (do
(redis/hincrby db views key 1) (redis/hincrby db views noteID 1)
text)))) text))))
(defn get-note-views (defn get-note-views
"Returns the number of views for the specified date and note title" [noteID]
[date title] (redis/hget db views noteID))
(redis/hget db views (build-key date title)))
(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? (defn note-exists?
"Returns true if the note with the specified title and date exists" [noteID]
[date title] (redis/hexists db note noteID))
(redis/hexists db note (build-key date title)))
(defn delete-note (defn delete-note
"Deletes the note with the specified coordinates" [noteID]
[date title] (doseq [kw [password views note published edited]]
(let [key (build-key date title)] ; TODO: delete short url by looking for the title
(doseq [kw [password views note]] (redis/hdel db kw noteID)))
; TODO: delete short url by looking for the title
(redis/hdel db kw key))))
(defn short-url-exists? (defn short-url-exists?
"Checks whether the provided short url is taken (for testing only)" "Checks whether the provided short url is taken (for testing only)"
[url] [url]
(redis/hexists db short-url url)) (redis/hexists db short-url url))
(defn get-short-url [noteID]
(redis/hget db short-url noteID))
(defn resolve-url (defn resolve-url
"Resolves short url by providing all metadata of the request" "Resolves short url by providing all metadata of the request"
[url] [url]
@ -97,19 +127,19 @@
(defn delete-short-url (defn delete-short-url
"Deletes a short url (for testing only)" "Deletes a short url (for testing only)"
[key] [noteID]
(let [value (redis/hget db short-url key)] (let [value (redis/hget db short-url noteID)]
(do (do
(redis/hdel db short-url key) (redis/hdel db short-url noteID)
(redis/hdel db short-url value)))) (redis/hdel db short-url value))))
(defn create-short-url (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" one if it was already created"
[metadata] [arg]
(let [request (str (into (sorted-map) metadata))] (let [key (if (map? arg) (str (into (sorted-map) arg)) arg)]
(if (short-url-exists? request) (if (short-url-exists? key)
(redis/hget db short-url request) (redis/hget db short-url key)
(let [hash-stream (partition 5 (repeatedly #(rand-int 36))) (let [hash-stream (partition 5 (repeatedly #(rand-int 36)))
hash-to-string (fn [hash] hash-to-string (fn [hash]
(apply str (apply str
@ -120,8 +150,8 @@
(remove short-url-exists? (remove short-url-exists?
(map hash-to-string hash-stream)))] (map hash-to-string hash-stream)))]
(do (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 ; 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 url key)
(redis/hset db short-url request url) (redis/hset db short-url key url)
url))))) url)))))

53
src/NoteHub/views/pages.clj

@ -1,7 +1,9 @@
(ns NoteHub.views.pages (ns NoteHub.views.pages
(:require [hiccup.util :as util]) (:require [hiccup.util :as util]
[NoteHub.api :as api]
[cheshire.core :refer :all])
(:use (:use
[NoteHub.storage] [NoteHub.storage] ; TODO: delete this
[NoteHub.settings] [NoteHub.settings]
[NoteHub.views.common] [NoteHub.views.common]
[clojure.string :rename {replace sreplace} [clojure.string :rename {replace sreplace}
@ -12,9 +14,7 @@
[hiccup.element] [hiccup.element]
[noir.response :only [redirect status content-type]] [noir.response :only [redirect status content-type]]
[noir.core :only [defpage defpartial]] [noir.core :only [defpage defpartial]]
[noir.statuses]) [noir.statuses]))
(:import
[java.util Calendar]))
(defn get-hash (defn get-hash
"A simple hash-function, which computes a hash from the text field "A simple hash-function, which computes a hash from the text field
@ -60,12 +60,6 @@
links (interpose separator links)] links (interpose separator links)]
[:div#panel (map identity 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 ; Routes
; ====== ; ======
@ -102,9 +96,10 @@
; Update Note Page ; Update Note Page
(defpage "/:year/:month/:day/:title/edit" {:keys [year month day title]} (defpage "/:year/:month/:day/:title/edit" {:keys [year month day title]}
(input-form "/update-note" :update (let [noteID (api/build-key [year month day] title)]
(html (hidden-field :key (build-key [year month day] title))) (input-form "/update-note" :update
(get-note [year month day] title) :enter-passwd)) (html (hidden-field :key noteID))
(get-note noteID) :enter-passwd)))
; New Note Page ; New Note Page
(defpage "/new" {} (defpage "/new" {}
@ -118,16 +113,16 @@
(wrap (wrap
(create-short-url params) (create-short-url params)
(select-keys params [:title :theme :header-font :text-font]) (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 ; Provides Markdown of the specified note
(defpage "/:year/:month/:day/:title/export" {:keys [year month day title]} (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))) (content-type "text/plain; charset=utf-8" md-text)))
; Provides the number of views of the specified note ; Provides the number of views of the specified note
(defpage "/:year/:month/:day/:title/stats" {:keys [year month day title]} (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) (layout (get-message :statistics)
[:table#stats.helvetica.central-element [:table#stats.helvetica.central-element
[:tr [:tr
@ -158,7 +153,7 @@
; if yes, we compute the current date, extract a title string from the text, ; 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; ; 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 ; 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 %)) untrimmed-line (filter #(or (= \- %) (Character/isLetterOrDigit %))
(-> draft split-lines first (sreplace " " "-") lower-case)) (-> draft split-lines first (sreplace " " "-") lower-case))
trim (fn [s] (apply str (drop-while #(= \- %) s))) trim (fn [s] (apply str (drop-while #(= \- %) s)))
@ -167,11 +162,11 @@
; TODO: replace to ccs/take when it gets fixed ; TODO: replace to ccs/take when it gets fixed
proposed-title (apply str (take max-length title-uncut)) proposed-title (apply str (take max-length title-uncut))
date [year month day] date [year month day]
title (first (drop-while #(note-exists? date %) title (first (drop-while #(note-exists? (api/build-key date %))
(cons proposed-title (cons proposed-title
(map #(str proposed-title "-" (+ 2 %)) (range)))))] (map #(str proposed-title "-" (+ 2 %)) (range)))))]
(do (do
(set-note date title draft password) (add-note (api/build-key date title) draft password)
(redirect (url year month day title)))) (redirect (url year month day title))))
(response 400)))) (response 400))))
@ -184,4 +179,22 @@
long-url (if (empty? rest-params) core-url (util/url core-url rest-params))] long-url (if (empty? rest-params) core-url (util/url core-url rest-params))]
(redirect long-url)))) (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)))

153
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)))))))

108
test/NoteHub/test/storage.clj

@ -1,5 +1,8 @@
(ns NoteHub.test.storage (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 date [2012 06 03])
(def test-title "Some title.") (def test-title "Some title.")
@ -13,54 +16,55 @@
(deftest storage (deftest storage
(testing "Storage" (testing "Storage"
(testing "of short-url mechanism" (testing "of short-url mechanism"
(let [url (create-short-url metadata) (let [url (create-short-url metadata)
url2 (create-short-url metadata)] url2 (create-short-url metadata)]
(is (short-url-exists? url)) (is (short-url-exists? url))
(is (= url url2)) (is (= url url2))
(is (= metadata (resolve-url url))) (is (= metadata (resolve-url url)))
(is (not (do (is (not (do
(delete-short-url url) (delete-short-url url)
(short-url-exists? url)))))) (short-url-exists? url))))))
(testing "of correct note creation" (testing "of correct note creation"
(is (= (do (is (= (do
(set-note date test-title test-note) (add-note (build-key date test-title) test-note)
(get-note date test-title)) (get-note (build-key date test-title)))
test-note)) test-note))
(is (= "1" (get-note-views date test-title))) (is (= "1" (get-note-views (build-key date test-title))))
(is (= (do (is (= (do
(get-note date test-title) (get-note (build-key date test-title))
(get-note-views date test-title)) (get-note-views (build-key date test-title)))
"2"))) "2")))
(testing "of note update" (testing "of note update"
(is (= (do (is (= (do
(set-note date test-title test-note "12345qwert") (add-note (build-key date test-title) test-note "12345qwert")
(get-note date test-title)) (get-note (build-key date test-title)))
test-note)) test-note))
(is (= (do (is (valid-password? (build-key date test-title) "12345qwert"))
(update-note (build-key date test-title) "update" "12345qwert") (is (= (do
(get-note date test-title)) (update-note (build-key date test-title) "update" "12345qwert")
"update")) (get-note (build-key date test-title)))
(is (= (do "update"))
(update-note (build-key date test-title) "not authorized" "44444") (is (= (do
(get-note date test-title)) (update-note (build-key date test-title) "not authorized" "44444")
"update"))) (get-note (build-key date test-title)))
(testing "of the note access" "update")))
(is (not= (get-note date test-title) "any text"))) (testing "of the note access"
(testing "session management" (is (not= (get-note (build-key date test-title)) "any text")))
(let [s1 (create-session) (testing "session management"
s2 (create-session) (let [s1 (create-session)
s3 (create-session)] s2 (create-session)
(is (invalidate-session s1)) s3 (create-session)]
(is (not (invalidate-session (str s1 s2)))) (is (invalidate-session s1))
(is (invalidate-session s2)) (is (not (invalidate-session (str s1 s2))))
(is (not (invalidate-session "wrongtoken"))) (is (invalidate-session s2))
(is (invalidate-session s3)))) (is (not (invalidate-session "wrongtoken")))
(testing "of note existence" (is (invalidate-session s3))))
(is (note-exists? date test-title)) (testing "of note existence"
(is (not (do (is (note-exists? (build-key date test-title)))
(delete-note date test-title) (is (not (do
(note-exists? date test-title)))) (delete-note (build-key date test-title))
(is (not (note-exists? [2013 06 03] test-title))) (note-exists? (build-key date test-title)))))
(is (not (note-exists? date "some title")))))) (is (not (note-exists? (build-key [2013 06 03] test-title))))
(is (not (note-exists? (build-key date "some title")))))))

25
test/NoteHub/test/views/pages.clj

@ -1,21 +1,20 @@
(ns NoteHub.test.views.pages (ns NoteHub.test.views.pages
(:use [NoteHub.views.pages] (:use [NoteHub.views.pages]
[NoteHub.api :only [build-key get-date]]
[noir.util.test] [noir.util.test]
[NoteHub.views.common :only [url]] [NoteHub.views.common :only [url]]
[NoteHub.storage] [NoteHub.storage]
[clojure.test])) [clojure.test]))
(defn substring? [a b] (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 date [2012 6 3])
(def test-title "some-title") (def test-title "some-title")
(def test-note "# This is a test note.\nHello _world_. Motörhead, тест.") (def test-note "# This is a test note.\nHello _world_. Motörhead, тест.")
(defn create-testnote-fixture [f] (defn create-testnote-fixture [f]
(set-note date test-title test-note) (add-note (build-key date test-title) test-note)
(f) (f)
(delete-note date test-title)) (delete-note (build-key date test-title)))
(use-fixtures :each create-testnote-fixture) (use-fixtures :each create-testnote-fixture)
@ -23,8 +22,8 @@
(deftest testing-fixture (deftest testing-fixture
(testing "Was a not created?" (testing "Was a not created?"
(is (= (get-note date test-title) test-note)) (is (= (get-note (build-key date test-title)) test-note))
(is (note-exists? date test-title)))) (is (note-exists? (build-key date test-title)))))
(deftest export-test (deftest export-test
(testing "Markdown export" (testing "Markdown export"
@ -42,12 +41,12 @@
{:session-key session-key {:session-key session-key
:draft test-note :draft test-note
:session-value (str (get-hash (str test-note session-key)))}) 302)) :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_" (is (substring? "Hello _world_"
((send-request (url year month day title)) :body))) ((send-request (url year month day title)) :body)))
(is (do (is (do
(delete-note date title) (delete-note (build-key date title))
(not (note-exists? date title))))))) (not (note-exists? (build-key date title))))))))
(deftest note-update (deftest note-update
(let [session-key (create-session) (let [session-key (create-session)
@ -62,7 +61,7 @@
:draft "test note" :draft "test note"
:password "qwerty" :password "qwerty"
:session-value (str (get-hash (str "test note" session-key)))}) 302)) :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" (is (substring? "test note"
((send-request (url year month day title)) :body))) ((send-request (url year month day title)) :body)))
(is (has-status (is (has-status
@ -82,8 +81,8 @@
(is (substring? "UPDATED CONTENT" (is (substring? "UPDATED CONTENT"
((send-request (url year month day title)) :body))) ((send-request (url year month day title)) :body)))
(is (do (is (do
(delete-note date title) (delete-note (build-key date title))
(not (note-exists? date title))))))) (not (note-exists? (build-key date title))))))))
(deftest requests (deftest requests
(testing "HTTP Status" (testing "HTTP Status"

Loading…
Cancel
Save