12 changed files with 466 additions and 142 deletions
@ -0,0 +1,105 @@
@@ -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))}))) |
||||
@ -0,0 +1,153 @@
@@ -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))))))) |
||||
Loading…
Reference in new issue