From f0b588d081a6192cc4446591b6605abdc33755c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCller?= Date: Tue, 19 Sep 2017 20:34:04 +0200 Subject: [PATCH] api tests added --- .gitignore | 2 - .jshintrc | 1 - .tern-project | 10 --- Gopkg.lock | 2 +- Makefile | 5 ++ api_spec.js | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 11 +++ server.go | 6 +- storage.go | 23 +++--- 9 files changed, 233 insertions(+), 28 deletions(-) delete mode 100644 .jshintrc delete mode 100644 .tern-project create mode 100644 Makefile create mode 100644 api_spec.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore index 7f5548e..6c34633 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -dump.rdb -bin/ node_modules/ npm-debug.log database.sqlite-journal diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 7b4964b..0000000 --- a/.jshintrc +++ /dev/null @@ -1 +0,0 @@ -{ "esnext": true } diff --git a/.tern-project b/.tern-project deleted file mode 100644 index 3cb638a..0000000 --- a/.tern-project +++ /dev/null @@ -1,10 +0,0 @@ -{ - "plugins": { - "node": {} - }, - "libs": [ - "ecma5", - "ecma6" - ], - "ecmaVersion": 6 -} diff --git a/Gopkg.lock b/Gopkg.lock index 0dc391d..e27a450 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -76,6 +76,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "82edf4a4585c9ad3318ead49e58d8bca7efed18e2f727be8e8fc6498972fc039" + inputs-digest = "441429b4f331d40009b166378491cbd1b7e797135151d16d79394725cfbe16a2" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a1721a4 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +run: + go run *.go + +test: + jasmine-node . diff --git a/api_spec.js b/api_spec.js new file mode 100644 index 0000000..c8e216c --- /dev/null +++ b/api_spec.js @@ -0,0 +1,201 @@ +var frisby = require('frisby'); + +frisby.create('Landing page') + .get('http://localhost:3000/') + .expectStatus(200) + .expectHeaderContains('content-type', 'text/html; charset=utf-8') + .expectBodyContains('Markdown Publishing') + .toss(); + +frisby.create('Open note page') + .get('http://localhost:3000/new') + .expectStatus(200) + .expectHeaderContains('content-type', 'text/html; charset=utf-8') + .expectBodyContains('Terms of Service') + .toss(); + +frisby.create('Open TOS') + .get('http://localhost:3000/TOS.md') + .expectStatus(200) + .expectHeaderContains('content-type', 'text/html; charset=utf-8') + .expectBodyContains('Site Terms of Use Modifications') + .toss(); + +frisby.create('Incurrect URL') + .get('http://localhost:3000/abcdef') + .expectStatus(404) + .expectBodyContains('Not found') + .toss(); + +frisby.create('Invalid posting 1') + .post('http://localhost:3000/note') + .expectStatus(412) + .expectHeaderContains('content-type', 'text/html; charset=utf-8') + .expectBodyContains('Precondition failed') + .toss(); + +frisby.create('Invalid posting 2') + .post('http://localhost:3000/note', { tos: "on" }) + .expectStatus(400) + .expectHeaderContains('content-type', 'text/html; charset=utf-8') + .expectBodyContains('Bad request') + .toss(); + +frisby.create('Invalid posting 3') + .post('http://localhost:3000/note', { + text: "too short", + password: '', + tos: 'on', + }) + .expectStatus(400) + .expectHeaderContains('content-type', 'text/html; charset=utf-8') + .expectBodyContains('length not accepted') + .toss(); + +let testNote = 'This is a test note'; + +frisby.create('Invalid posting 4') + .post('http://localhost:3000/note', { + note: testNote + }) + .expectStatus(412) + .expectHeaderContains('content-type', 'text/html; charset=utf-8') + .expectBodyContains('Precondition failed') + .toss(); + +frisby.create('Valid posting') + .post('http://localhost:3000/note', { + password: '', + tos: 'on', + text: testNote + }) + .expectStatus(301) + .expectHeaderContains('content-type', 'text/plain; charset=utf-8') + .expectHeaderContains('Location', '/') + .after(function(err, res, body) { + let noteId = res.headers.location.replace('/', ''); + frisby.create('Read posted note') + .get('http://localhost:3000/' + noteId) + .expectStatus(200) + .expectBodyContains(testNote) + .after((err, res, body) => { + frisby.create('Illegal note editing attempt with empty password') + .post('http://localhost:3000/note', { + id: noteId, + tos: 'on', + action: 'UPDATE', + text: testNote + '!!!', + password: '' + }) + .expectStatus(400) + .expectBodyContains('password is empty') + .toss(); + }) + .after((err, res, body) => { + frisby.create('Illegal note editing attempt') + .post('http://localhost:3000/note', { + id: noteId, + tos: 'on', + action: 'UPDATE', + text: testNote + '!!!', + password: 'aaabbb' + }) + .expectStatus(401) + .expectBodyContains('id or password is wrong') + .toss(); + + }) + .toss(); + }) + .toss(); + +frisby.create('Valid posting, editing and more') + .post('http://localhost:3000/note', { + password: 'aabbcc', + tos: 'on', + text: testNote + }) + .expectStatus(301) + .expectHeaderContains('Location', '/') + .expectHeaderContains('content-type', 'text/plain; charset=utf-8') + .after(function(err, res, body) { + let noteId = res.headers.location.replace('/', ''); + frisby.create('Export posted note') + .get('http://localhost:3000/' + noteId + '/export') + .expectStatus(200) + .expectHeaderContains('content-type', 'text/plain; charset=utf-8') + .expectBodyContains(testNote) + .toss(); + frisby.create('Read posted note') + .get('http://localhost:3000/' + noteId) + .expectStatus(200) + .expectBodyContains(testNote) + .expectHeaderContains('content-type', 'text/html; charset=utf-8') + .after((err, res, body) => { + frisby.create('Unauthorized note editing attempt') + .post('http://localhost:3000/note', { + id: noteId, + tos: 'on', + text: testNote + '!!!', + password: 'abbcc' + }) + .expectStatus(401) + .expectBodyContains('password is wrong') + .toss(); + }) + .after((err, res, body) => { + frisby.create('Valid note editing attempt') + .post('http://localhost:3000/note', { + id: noteId, + tos: 'on', + text: 'Changed text!', + password: 'aabbcc' + }) + .expectStatus(301) + .after((err, res, body) => { + frisby.create('Read changed note') + .get('http://localhost:3000/' + noteId) + .expectStatus(200) + .expectBodyContains('Changed text!') + .toss(); + }) + .toss(); + }) + .toss(); + frisby.create('Read export of posted note') + .expectStatus(200) + .get('http://localhost:3000/' + noteId + '/export') + .expectHeaderContains('content-type', 'text/plain; charset=utf-8') + .expectBodyContains(testNote) + .toss(); + frisby.create('Open /edit on posted note') + .expectStatus(200) + .expectBodyContains('') + .get('http://localhost:3000/' + noteId + '/edit') + .toss(); + frisby.create('Read stats of posted note') + .get('http://localhost:3000/' + noteId + '/stats') + .expectHeaderContains('content-type', 'text/html; charset=utf-8') + .expectStatus(200) + .expectBodyContains('Statistics') + .expectBodyContains('Views0') + .toss(); + }) + .toss(); + +var tooLongNote = 'ABCD'; + +while (tooLongNote.length < 1024*200) tooLongNote += tooLongNote; + +frisby.create('Invalid posting of too long note') + .post('http://localhost:3000/note', { + action: 'POST', + tos: 'on', + password: 'aabbcc', + text: tooLongNote + }) + .expectStatus(400) + .expectHeaderContains('content-type', 'text/html; charset=utf-8') + .expectBodyContains('length not accepted') + .toss(); + diff --git a/package.json b/package.json new file mode 100644 index 0000000..4856739 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "NoteHub", + "version": "3.0.0", + "main": "server.js", + "scripts": { + "test": "jasmine-node ." + }, + "devDependencies": { + "frisby": "^0.8.5" + } +} diff --git a/server.go b/server.go index b3294c5..462d829 100644 --- a/server.go +++ b/server.go @@ -142,7 +142,7 @@ func main() { code = http.StatusBadRequest } c.Logger().Errorf("POST /note error: %d", code) - return c.Render(code, "Note", errPage(code)) + return c.Render(code, "Note", errPage(code, err.Error())) } c.Logger().Debugf("note %q saved", n.ID) return c.Redirect(http.StatusMovedPermanently, "/"+n.ID) @@ -160,7 +160,7 @@ func get(vals url.Values, key string) string { return "" } -func md2html(c echo.Context, name string) (Note, int) { +func md2html(c echo.Context, name string) (*Note, int) { path := "assets/markdown/" + name + ".md" mdContent, err := ioutil.ReadFile(path) if err != nil { @@ -169,5 +169,5 @@ func md2html(c echo.Context, name string) (Note, int) { return errPage(code), code } c.Logger().Debugf("rendering markdown page %q", name) - return Note{Title: name, Content: mdTmplHTML(mdContent)}, http.StatusOK + return &Note{Title: name, Content: mdTmplHTML(mdContent)}, http.StatusOK } diff --git a/storage.go b/storage.go index 4228ec2..26456b2 100644 --- a/storage.go +++ b/storage.go @@ -54,15 +54,18 @@ type Note struct { Content, Ads template.HTML } -func errPage(code int, details ...string) Note { +func errPage(code int, details ...string) *Note { text := errorCodes[code] + body := text if len(details) > 0 { - text += ": " + strings.Join(details, ";") + body += ": " + strings.Join(details, ";") } - return Note{ - Title: text, - Content: mdTmplHTML([]byte(fmt.Sprintf("# %d %s", code, text))), + n := &Note{ + Title: text, + Text: fmt.Sprintf("# %d %s", code, body), } + n.prepare() + return n } func (n *Note) prepare() { @@ -88,13 +91,11 @@ func persistStats(logger echo.Logger, db *sql.DB, stats *sync.Map) { stats.Range(func(id, views interface{}) bool { stmt, _ := tx.Prepare("update notes set views = ? where id = ?") _, err := stmt.Exec(views, id) - if err != nil { - tx.Rollback() - return false + if err == nil { + c++ } stmt.Close() defer stats.Delete(id) - c++ return true }) tx.Commit() @@ -174,7 +175,7 @@ func randId() string { return buf.String() } -func load(c echo.Context, db *sql.DB) (Note, int) { +func load(c echo.Context, db *sql.DB) (*Note, int) { q := c.Param("id") c.Logger().Debugf("loading note %q", q) stmt, _ := db.Prepare("select * from notes where id = ?") @@ -198,7 +199,7 @@ func load(c echo.Context, db *sql.DB) (Note, int) { if editedVal != nil { n.Edited = editedVal.(time.Time) } - return *n, http.StatusOK + return n, http.StatusOK } var mdRenderer = markdown.New(markdown.HTML(true))