Browse Source

migrated to API internally

master
Christian Mueller 12 years ago
parent
commit
a245dd8bcf
  1. 18
      API.md
  2. 4
      messages
  3. 28
      resources/public/js/main.js
  4. 1
      settings
  5. 38
      src/NoteHub/api.clj
  6. 3
      src/NoteHub/server.clj
  7. 14
      src/NoteHub/storage.clj
  8. 125
      src/NoteHub/views/pages.clj
  9. 6
      test/NoteHub/test/api.clj
  10. 6
      test/NoteHub/test/storage.clj
  11. 46
      test/NoteHub/test/views/pages.clj

18
API.md

@ -26,14 +26,14 @@ Parameter | Explanation | Type
`noteID` | Note-ID | **required** `noteID` | Note-ID | **required**
`version` | Used API version | **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`, `longPath`, `shortPath`, `statistics`, `status`.
Example: Example:
{ {
note: <markdown source>, note: <markdown source>,
longURL: "http://notehub.org/2014/1/3/lorem-ipsum", longPath: "/2014/1/3/lorem-ipsum",
shortURL: "http://notehub.org/0vrcp", shortPath: "/0vrcp",
statistics: { statistics: {
published: "2014-1-3", published: "2014-1-3",
edited: "2014-1-12", edited: "2014-1-12",
@ -71,14 +71,14 @@ The Signature is the MD5 hash of the following string concatenation:
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 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`. The response of the server will contain the fields `noteID`, `longPath`, `shortPath`, `status`.
Example: Example:
{ {
noteID: "2014/1/3/lorem-ipsum", noteID: "2014/1/3/lorem-ipsum",
longURL: "http://notehub.org/2014/1/3/lorem-ipsum", longPath: "/2014/1/3/lorem-ipsum",
shortURL: "http://notehub.org/0vrcp", shortPath: "/0vrcp",
status: { status: {
success: true, success: true,
comment: "some server message" comment: "some server message"
@ -109,13 +109,13 @@ The Signature is the MD5 hash of the following string concatenation:
pid + psk + noteID + note + password pid + psk + noteID + note + password
The response of the server will contain the fields `longURL`, `shortURL`, `status`. The response of the server will contain the fields `longPath`, `shortPath`, `status`.
Example: Example:
{ {
longURL: "http://notehub.org/2014/1/3/lorem-ipsum", longPath: "/2014/1/3/lorem-ipsum",
shortURL: "http://notehub.org/0vrcp", shortPath: "/0vrcp",
status: { status: {
success: true, success: true,
comment: "some server message" comment: "some server message"

4
messages

@ -1,4 +1,4 @@
page-title = Publish Markdown for Free page-title = Pastebin for Markdown: Publish Your Markdown Anonymously
title = Free and Hassle-free Pastebin for Markdown Pages. title = Free and Hassle-free Pastebin for Markdown Pages.
name = NoteHub name = NoteHub
new-page = New Page new-page = New Page
@ -23,4 +23,4 @@ stats = statistics
export = export export = export
edit = edit edit = edit
short-url = short url short-url = short url
api-title = NoteHub API api-title = API

28
resources/public/js/main.js

@ -1,36 +1,24 @@
var hash = function (input) {
var shortMod = function(i) { return i % 32767 };
var charCodes = input.split("")
.filter(function(c){ return c != "\n" && c != "\r" })
.map(function(c){ return c.charCodeAt(0) });
var h = 0;
for(var i in charCodes)
h = shortMod(h + shortMod(charCodes[i] * (h % 2 != 0 ? 16381 ^ i : 16381 & i)));
return h;
}
var $ = function(id){ return document.getElementById(id); } var $ = function(id){ return document.getElementById(id); }
var iosDetected = navigator.userAgent.match("(iPad|iPod|iPhone)"); var iosDetected = navigator.userAgent.match("(iPad|iPod|iPhone)");
var timer = null; var timer = null;
var timerDelay = iosDetected ? 800 : 400; 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 $note, $action, $preview, $plain_password, $input_elems, $dashed_line, updatePreview;
function md2html(input){ function md2html(input){
return marked(input); return marked(input);
} }
function loadPage() { function loadPage() {
$draft = $("draft"); $note = $("note");
$action = $("action"); $action = $("action");
$preview = $("preview"); $preview = $("preview");
$password = $("password");
$plain_password = $("plain-password"); $plain_password = $("plain-password");
$input_elems = $("input-elems"); $input_elems = $("input-elems");
$dashed_line = $("dashed-line"); $dashed_line = $("dashed-line");
updatePreview = function(){ updatePreview = function(){
clearTimeout(timer); clearTimeout(timer);
var content = $draft.value; var content = $note.value;
var delay = Math.min(timerDelay, timerDelay * (content.length / 400)); var delay = Math.min(timerDelay, timerDelay * (content.length / 400));
timer = setTimeout(function(){ timer = setTimeout(function(){
show($dashed_line); show($dashed_line);
@ -39,14 +27,14 @@ function loadPage() {
}, delay); }, delay);
}; };
if($action){ if($action){
if($action.value == "update") updatePreview(); else $draft.value = ""; if($action.value == "update") updatePreview(); else $note.value = "";
$draft.onkeyup = updatePreview; $note.onkeyup = updatePreview;
$("publish-button").onclick = function(e) { $("publish-button").onclick = function(e) {
if($plain_password.value != "") $password.value = md5($plain_password.value); if($plain_password.value != "") $("password").value = md5($plain_password.value);
$plain_password.value = null; $plain_password.value = null;
$("session-value").value = hash($draft.value + $("session-key").value); $("signature").value = md5($("session").value + $note.value);
} }
if(iosDetected) $draft.className += " ui-border"; else $draft.focus(); if(iosDetected) $note.className += " ui-border"; else $note.focus();
} }
var mdDocs = document.getElementsByClassName("markdown"); var mdDocs = document.getElementsByClassName("markdown");

1
settings

@ -1 +1,2 @@
max-title-length = 40 max-title-length = 40
domain = http://notehub.org/

38
src/NoteHub/api.clj

@ -7,9 +7,9 @@
:only [replace blank? lower-case split-lines]]) :only [replace blank? lower-case split-lines]])
(:require [NoteHub.storage :as storage])) (:require [NoteHub.storage :as storage]))
(def api-version "1.0") (def version "1.0")
(def domain "http://notehub.org/") (def domain (get-setting :domain))
; Concatenates all fields to a string ; Concatenates all fields to a string
(defn build-key (defn build-key
@ -28,10 +28,10 @@
([success message & params] ([success message & params]
(assoc (create-response success) :message (apply format message params)))) (assoc (create-response success) :message (apply format message params))))
(defn- getURL [noteID & description] (defn- getPath [noteID & description]
(if description (if description
(str domain (storage/get-short-url noteID)) (str "/" (storage/get-short-url noteID))
(str domain (sreplace noteID #" " "/")))) (str "/" (sreplace noteID #" " "/"))))
(let [md5Instance (java.security.MessageDigest/getInstance "MD5")] (let [md5Instance (java.security.MessageDigest/getInstance "MD5")]
(defn get-signature (defn get-signature
@ -43,13 +43,13 @@
(.toString (new java.math.BigInteger 1 (.digest md5Instance)) 16))))) (.toString (new java.math.BigInteger 1 (.digest md5Instance)) 16)))))
(defn get-note [noteID] (defn get-note [noteID]
(if (storage/note-exists? noteID)
{:note (storage/get-note noteID) {:note (storage/get-note noteID)
:longURL (getURL noteID) :longPath (getPath noteID)
:shortURL (getURL noteID :short) :shortPath (getPath noteID :short)
:statistics (storage/get-note-statistics noteID) :statistics (storage/get-note-statistics noteID)
:status (if (storage/note-exists? noteID) :status (create-response true)}
(create-response true) (create-response false "noteID '%s' unknown" noteID)))
(create-response false "noteID '%s' unknown" noteID))})
(defn post-note (defn post-note
([note pid signature] (post-note note pid signature nil)) ([note pid signature] (post-note note pid signature nil))
@ -76,12 +76,10 @@
(do (do
(storage/add-note noteID note password) (storage/add-note noteID note password)
(storage/create-short-url noteID) (storage/create-short-url noteID)
{ {:noteID noteID
:noteID noteID :longPath (getPath noteID)
:longURL (getURL noteID) :shortPath (getPath noteID :short)
:shortURL (getURL noteID :short) :status (create-response true)}))
:status (create-response true)
}))
{:status (create-response false (first errors))})))) {:status (create-response false (first errors))}))))
@ -97,9 +95,7 @@
(if (empty? errors) (if (empty? errors)
(do (do
(storage/edit-note noteID note) (storage/edit-note noteID note)
{ {:longPath (getPath noteID)
:longURL (getURL noteID) :shortPath (getPath noteID :short)
:shortURL (getURL noteID :short) :status (create-response true)})
:status (create-response true)
})
{:status (create-response false (first errors))}))) {:status (create-response false (first errors))})))

3
src/NoteHub/server.clj

@ -6,6 +6,5 @@
(defn -main [& m] (defn -main [& m]
(let [mode (keyword (or (first m) :prod)) (let [mode (keyword (or (first m) :prod))
port (Integer. (get (System/getenv) "PORT" "8080"))] port (Integer. (get (System/getenv) "PORT" "8080"))]
(server/start port {:mode mode (server/start port {:mode mode :ns 'NoteHub})))
:ns 'NoteHub})))

14
src/NoteHub/storage.clj

@ -40,28 +40,18 @@
(defn get-psk [pid] (defn get-psk [pid]
(redis/hget db publisher pid)) (redis/hget db publisher pid))
(defn create-session (defn create-session []
[]
(let [token (encrypt (str (rand-int Integer/MAX_VALUE)))] (let [token (encrypt (str (rand-int Integer/MAX_VALUE)))]
(do (redis/sadd db sessions token) (do (redis/sadd db sessions token)
token))) token)))
(defn invalidate-session (defn invalidate-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) (do (redis/srem db sessions token)
was-valid)))) was-valid))))
; TODO: deprecated
(defn update-note
[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 edit-note (defn edit-note
[noteID text] [noteID text]
(do (do

125
src/NoteHub/views/pages.clj

@ -4,12 +4,12 @@
[NoteHub.storage :as storage] [NoteHub.storage :as storage]
[cheshire.core :refer :all]) [cheshire.core :refer :all])
(:use (:use
[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}
:only [escape split replace blank? split-lines lower-case]] :only [escape split replace blank? split-lines lower-case]]
[clojure.core.incubator :only [-?>]] [clojure.core.incubator :only [-?>]]
[noir.util.crypt :only [encrypt]]
[hiccup.form] [hiccup.form]
[hiccup.core] [hiccup.core]
[hiccup.element] [hiccup.element]
@ -17,21 +17,8 @@
[noir.core :only [defpage defpartial]] [noir.core :only [defpage defpartial]]
[noir.statuses])) [noir.statuses]))
(defn get-hash (when-not (storage/valid-publisher? api/domain)
"A simple hash-function, which computes a hash from the text field (storage/register-publisher api/domain))
content and given session number. It is intended to be used as a spam
protection / captcha alternative. (Probably doesn't work for UTF-16)"
[s]
(let [short-mod #(mod % 32767)
char-codes (map #(.codePointAt % 0) (remove #(contains? #{"\n" "\r"} %) (map str s)))
zip-with-index (map list char-codes (range))]
(reduce
#(short-mod (+ %
(short-mod (* (first %2)
((if (odd? %)
bit-xor
bit-and) 16381 (second %2))))))
0 zip-with-index)))
; Sets a custom message for each needed HTTP status. ; Sets a custom message for each needed HTTP status.
; The message to be assigned is extracted with a dynamically generated key ; The message to be assigned is extracted with a dynamically generated key
@ -70,15 +57,19 @@
[:div.central-element.helvetica {:style "margin-bottom: 3em"} [:div.central-element.helvetica {:style "margin-bottom: 3em"}
(form-to {:autocomplete :off} [:post form-url] (form-to {:autocomplete :off} [:post form-url]
(hidden-field :action command) (hidden-field :action command)
(hidden-field :version api/version)
(hidden-field :password) (hidden-field :password)
fields fields
(text-area {:class :max-width} :draft content) (text-area {:class :max-width} :note content)
[:fieldset#input-elems {:class css-class} [:fieldset#input-elems {:class css-class}
(text-field {:class "ui-elem" :placeholder (get-message passwd-msg)} (text-field {:class "ui-elem" :placeholder (get-message passwd-msg)}
:plain-password) :plain-password)
(submit-button {:class "button ui-elem" (submit-button {:class "button ui-elem"
:id :publish-button} (get-message command))])]))) :id :publish-button} (get-message command))])])))
(defn generate-session []
(encrypt (str (rand-int Integer/MAX_VALUE))))
; Routes ; Routes
; ====== ; ======
@ -98,24 +89,10 @@
; Displays the note ; Displays the note
(defpage "/:year/:month/:day/:title" {:keys [year month day title theme header-font text-font] :as params} (defpage "/:year/:month/:day/:title" {:keys [year month day title theme header-font text-font] :as params}
(wrap (wrap
(create-short-url params) (storage/create-short-url params)
(select-keys params [:title :theme :header-font :text-font]) (select-keys params [:title :theme :header-font :text-font])
(:note (api/get-note (api/build-key [year month day] title))))) (:note (api/get-note (api/build-key [year month day] title)))))
; Update Note Page
(defpage "/:year/:month/:day/:title/edit" {:keys [year month day title]}
(let [noteID (api/build-key [year month day] title)]
(input-form "/update-note" :update
(html (hidden-field :key noteID))
(:note (api/get-note noteID)) :enter-passwd)))
; New Note Page
(defpage "/new" {}
(input-form "/post-note" :publish
(html (hidden-field :session-key (create-session))
(hidden-field {:id :session-value} :session-value))
(get-message :loading) :set-passwd))
; 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 (:note (api/get-note (api/build-key [year month day] title)))] (when-let [md-text (:note (api/get-note (api/build-key [year month day] title)))]
@ -137,53 +114,55 @@
[:td (get-message :article-views)] [:td (get-message :article-views)]
[:td (:views stats)]]]))) [:td (:views stats)]]])))
; Updates a note
(defpage [:post "/update-note"] {:keys [key draft password]}
(if (update-note key draft password)
(redirect (apply url (split key #" ")))
(response 403)))
; New Note Posting — the most "complex" function in the entire app ;)
(defpage [:post "/post-note"] {:keys [draft password session-key session-value]}
; first we collect all info needed to evaluate the validity of the note creation request
(let [valid-session (invalidate-session session-key) ; was the note posted from a newly generated form?
valid-draft (not (blank? draft)) ; has the note a meaningful content?
; is the hash code correct?
valid-hash (try
(= (Short/parseShort session-value)
(get-hash (str draft session-key)))
(catch Exception e nil))]
; check whether the new note can be added
(if (and valid-session valid-draft valid-hash)
; 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] (api/get-date)
untrimmed-line (filter #(or (= \- %) (Character/isLetterOrDigit %))
(-> draft 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)
; 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? (api/build-key date %))
(cons proposed-title
(map #(str proposed-title "-" (+ 2 %)) (range)))))]
(do
(add-note (api/build-key date title) draft password)
(redirect (url year month day title))))
(response 400))))
; Resolving of a short url ; Resolving of a short url
(defpage "/:short-url" {:keys [short-url]} (defpage "/:short-url" {:keys [short-url]}
(when-let [params (resolve-url short-url)] (when-let [params (storage/resolve-url short-url)]
(let [{:keys [year month day title]} params (let [{:keys [year month day title]} params
rest-params (dissoc params :year :month :day :title) rest-params (dissoc params :year :month :day :title)
core-url (url year month day title) core-url (url year month day title)
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))))
; New Note Page
(defpage "/new" {}
(input-form "/post-note" :publish
(html (hidden-field :session (storage/create-session))
(hidden-field {:id :signature} :signature))
(get-message :loading) :set-passwd))
; Update Note Page
(defpage "/:year/:month/:day/:title/edit" {:keys [year month day title]}
(let [noteID (api/build-key [year month day] title)]
(input-form "/update-note" :update
(html (hidden-field :noteID noteID))
(:note (api/get-note noteID)) :enter-passwd)))
; Creates New Note from Web
(defpage [:post "/post-note"] {:keys [session note signature password version]}
(if (= signature (api/get-signature session note))
(let [pid api/domain
psk (storage/get-psk pid)]
(if (storage/valid-publisher? pid)
(let [resp (api/post-note note pid (api/get-signature (str pid psk note)) password)]
(if (get-in resp [:status :success])
(redirect (:longPath resp))
(response 400)))
(response 500)))
(response 400)))
; Updates a note
(defpage [:post "/update-note"] {:keys [noteID note password version]}
(let [pid api/domain
psk (storage/get-psk pid)]
(if (storage/valid-publisher? pid)
(let [resp (api/update-note noteID note pid
(api/get-signature (str pid psk noteID note password))
password)]
(if (get-in resp [:status :success])
(redirect (:longPath resp))
(response 403)))
(response 500))))
; Here lives the API ; Here lives the API
(defpage "/api" args (defpage "/api" args
@ -199,7 +178,3 @@
(defpage [:put "/api/note"] {:keys [version noteID 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))) (generate-string (api/update-note noteID note pid signature password)))

6
test/NoteHub/test/api.clj

@ -10,7 +10,7 @@
(def pid "somePlugin") (def pid "somePlugin")
(def pid2 "somePlugin2") (def pid2 "somePlugin2")
(def note-title (str (apply print-str (get-date)) " hello-world-this-is-a-test-note")) (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")) (def note-url (str "/" (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)))) (defn substring? [a b] (not (= nil (re-matches (re-pattern (str "(?s).*" a ".*")) b))))
(defmacro isnt [arg] `(is (not ~arg))) (defmacro isnt [arg] `(is (not ~arg)))
@ -43,8 +43,8 @@
(is (:success (:status post-response))) (is (:success (:status post-response)))
(is (:success (:status get-response))) (is (:success (:status get-response)))
(is (= note (:note get-response))) (is (= note (:note get-response)))
(is (= (:longURL post-response) (:longURL get-response) note-url)) (is (= (:longPath post-response) (:longPath get-response) note-url))
(is (= (:shortURL post-response) (:shortURL get-response))) (is (= (:shortPath post-response) (:shortPath get-response)))
(is (= "1" (get-in get-response [:statistics :views]))) (is (= "1" (get-in get-response [:statistics :views])))
(isnt (get-in get-response [:statistics :edited])) (isnt (get-in get-response [:statistics :edited]))
(is (= "2" (get-in (get-note (:noteID post-response)) [:statistics :views]))))) (is (= "2" (get-in (get-note (:noteID post-response)) [:statistics :views])))))

6
test/NoteHub/test/storage.clj

@ -43,11 +43,7 @@
test-note)) test-note))
(is (valid-password? (build-key date test-title) "12345qwert")) (is (valid-password? (build-key date test-title) "12345qwert"))
(is (= (do (is (= (do
(update-note (build-key date test-title) "update" "12345qwert") (edit-note (build-key date test-title) "update")
(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))) (get-note (build-key date test-title)))
"update"))) "update")))
(testing "of the note access" (testing "of the note access"

46
test/NoteHub/test/views/pages.clj

@ -1,6 +1,6 @@
(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]] [NoteHub.api :only [build-key get-signature get-date]]
[noir.util.test] [noir.util.test]
[NoteHub.views.common :only [url]] [NoteHub.views.common :only [url]]
[NoteHub.storage] [NoteHub.storage]
@ -38,9 +38,9 @@
(is (has-status (is (has-status
(send-request (send-request
[:post "/post-note"] [:post "/post-note"]
{:session-key session-key {:session session-key
:draft test-note :note test-note
:session-value (str (get-hash (str test-note session-key)))}) 302)) :signature (get-signature session-key test-note)}) 302))
(is (note-exists? (build-key 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)))
@ -51,35 +51,33 @@
(deftest note-update (deftest note-update
(let [session-key (create-session) (let [session-key (create-session)
date (get-date) date (get-date)
title "test-note" title "this-is-a-test-note"
[year month day] date] [year month day] date
hash (get-signature session-key test-note)]
(testing "Note update" (testing "Note update"
(is (has-status (is (has-status
(send-request (send-request
[:post "/post-note"] [:post "/post-note"]
{:session-key session-key {:session session-key
:draft "test note" :note test-note
:password "qwerty" :password "qwerty"
:session-value (str (get-hash (str "test note" session-key)))}) 302)) :signature hash}) 302))
(is (note-exists? (build-key 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
(send-request (send-request
[:post "/update-note"] [:post "/update-note"]
{:key (build-key [year month day] title) {:noteID (build-key [year month day] title)
:draft "WRONG pass" :note "WRONG pass"
:password "qwerty1" }) 403)) :password "qwerty1" }) 403))
(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
(send-request (send-request
[:post "/update-note"] [:post "/update-note"]
{:key (build-key [year month day] title) {:noteID (build-key [year month day] title)
:draft "UPDATED CONTENT" :note "UPDATED CONTENT 123"
:password "qwerty" }) 302)) :password "qwerty" }) 302))
(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 (build-key date title)) (delete-note (build-key date title))
(not (note-exists? (build-key date title)))))))) (not (note-exists? (build-key date title))))))))
@ -100,13 +98,3 @@
(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" "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 (url 2012 6 3 "some-title" "stats")) 200) "accessing test note's stats")
(is (has-status (send-request "/") 200) "accessing landing page")))) (is (has-status (send-request "/") 200) "accessing landing page"))))
(deftest hash-function
(testing "Self-made hash function"
(testing "for correct hashes"
(is (= 0 (get-hash "")))
(is (= 6178 (get-hash "test тест")))
(is (= 6178 (get-hash (str "test\n \rтест"))))
(is (= 274 (get-hash "Hello world!"))))
(testing "for a wrong hash"
(is (not= 6178 (get-hash "wrong hash"))))))

Loading…
Cancel
Save