Browse Source

adds frisby integration tests

master
Christian Müller 8 years ago
parent
commit
bd2d8f0137
  1. 5
      Makefile
  2. 4
      README.md
  3. 18
      server.go
  4. 32
      stats.go
  5. 344
      test/main.go

5
Makefile

@ -1,5 +1,8 @@
run: run:
SKIP_CAPTCHA=1 go run *.go TEST_MODE=1 go run *.go
tests:
go run test/main.go
db: db:
echo 'CREATE TABLE "notes" (`id` VARCHAR(6) UNIQUE PRIMARY KEY, `text` TEXT, `published` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `edited` TIMESTAMP DEFAULT NULL, `password` VARCHAR(16), `views` INTEGER DEFAULT 0);' | sqlite3 database.sqlite echo 'CREATE TABLE "notes" (`id` VARCHAR(6) UNIQUE PRIMARY KEY, `text` TEXT, `published` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `edited` TIMESTAMP DEFAULT NULL, `password` VARCHAR(16), `views` INTEGER DEFAULT 0);' | sqlite3 database.sqlite

4
README.md

@ -22,5 +22,5 @@ Dead simple hosting for markdown notes.
- `NOTEHUB_ADMIN_EMAIL` - `NOTEHUB_ADMIN_EMAIL`
- Recaptcha secret: - Recaptcha secret:
- `RECAPTCHA_SECRET` - `RECAPTCHA_SECRET`
- Debugging: - Test mode:
- `SKIP_CAPTCHA` (expected to be non-empty) - `TEST_MODE` (expected to be non-empty; skips captcha, no writes buffering for stats)

18
server.go

@ -22,6 +22,8 @@ import (
const fraudThreshold = 7 const fraudThreshold = 7
var TEST_MODE = false
type Template struct{ templates *template.Template } type Template struct{ templates *template.Template }
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
@ -38,7 +40,7 @@ func main() {
} }
defer db.Close() defer db.Close()
skipCaptcha := os.Getenv("SKIP_CAPTCHA") != "" TEST_MODE = os.Getenv("TEST_MODE") != ""
var ads []byte var ads []byte
adsFName := os.Getenv("ADS") adsFName := os.Getenv("ADS")
@ -50,7 +52,7 @@ func main() {
} }
} }
go persistStats(e.Logger, db) go flushStatsLoop(e.Logger, db)
e.Renderer = &Template{templates: template.Must(template.ParseGlob("assets/templates/*.html"))} e.Renderer = &Template{templates: template.Must(template.ParseGlob("assets/templates/*.html"))}
@ -77,7 +79,7 @@ func main() {
if code != http.StatusOK { if code != http.StatusOK {
return c.String(code, statuses[code]) return c.String(code, statuses[code])
} }
defer incViews(n) defer incViews(n, db)
if fraudelent(n) { if fraudelent(n) {
n.Ads = mdTmplHTML(ads) n.Ads = mdTmplHTML(ads)
} }
@ -87,14 +89,16 @@ func main() {
e.GET("/:id/export", func(c echo.Context) error { e.GET("/:id/export", func(c echo.Context) error {
id := c.Param("id") id := c.Param("id")
n, code := load(c, db) n, code := load(c, db)
content := statuses[code] var content string
if code == http.StatusOK { if code == http.StatusOK {
defer incViews(n) defer incViews(n, db)
if fraudelent(n) { if fraudelent(n) {
code = http.StatusForbidden code = http.StatusForbidden
} content = statuses[code]
} else {
content = n.Text content = n.Text
} }
}
c.Logger().Debugf("/%s/export requested; response code: %d", id, code) c.Logger().Debugf("/%s/export requested; response code: %d", id, code)
return c.String(code, content) return c.String(code, content)
}) })
@ -139,7 +143,7 @@ func main() {
e.POST("/", func(c echo.Context) error { e.POST("/", func(c echo.Context) error {
c.Logger().Debug("POST /") c.Logger().Debug("POST /")
if !skipCaptcha && !checkRecaptcha(c, c.FormValue("token")) { if !TEST_MODE && !checkRecaptcha(c, c.FormValue("token")) {
code := http.StatusForbidden code := http.StatusForbidden
return c.JSON(code, postResp{false, statuses[code] + ": robot check failed"}) return c.JSON(code, postResp{false, statuses[code] + ": robot check failed"})
} }

32
stats.go

@ -12,15 +12,26 @@ const statsSavingInterval = 1 * time.Minute
var stats = &sync.Map{} var stats = &sync.Map{}
func persistStats(logger echo.Logger, db *sql.DB) { func flushStatsLoop(logger echo.Logger, db *sql.DB) {
for { for {
c, err := flush(db)
if err != nil {
logger.Errorf("couldn't flush stats: %v", err)
}
if c > 0 {
logger.Infof("successfully persisted %d values", c)
}
time.Sleep(statsSavingInterval) time.Sleep(statsSavingInterval)
}
}
func flush(db *sql.DB) (int, error) {
c := 0
tx, err := db.Begin() tx, err := db.Begin()
if err != nil { if err != nil {
logger.Error(err) return c, err
return
} }
c := 0
stats.Range(func(id, views interface{}) bool { stats.Range(func(id, views interface{}) bool {
stmt, _ := tx.Prepare("update notes set views = ? where id = ?") stmt, _ := tx.Prepare("update notes set views = ? where id = ?")
_, err := stmt.Exec(views, id) _, err := stmt.Exec(views, id)
@ -31,14 +42,10 @@ func persistStats(logger echo.Logger, db *sql.DB) {
defer stats.Delete(id) defer stats.Delete(id)
return true return true
}) })
tx.Commit() return c, tx.Commit()
if c > 0 {
logger.Infof("successfully persisted %d values", c)
}
}
} }
func incViews(n *Note) { func incViews(n *Note, db *sql.DB) {
views := n.Views views := n.Views
if val, ok := stats.Load(n.ID); ok { if val, ok := stats.Load(n.ID); ok {
intVal, ok := val.(int) intVal, ok := val.(int)
@ -46,5 +53,8 @@ func incViews(n *Note) {
views = intVal views = intVal
} }
} }
defer stats.Store(n.ID, views+1) stats.Store(n.ID, views+1)
if TEST_MODE {
flush(db)
}
} }

344
test/main.go

@ -0,0 +1,344 @@
package main
import (
"strings"
simplejson "github.com/bitly/go-simplejson"
"github.com/verdverm/frisby"
)
func main() {
service := "http://localhost:3000"
frisby.Create("Test Notehub landing page").
Get(service).
Send().
ExpectHeader("Content-Type", "text/html; charset=utf-8").
ExpectStatus(200).
ExpectContent("Pastebin for One-Off Markdown Publishing")
frisby.Create("Test Notehub TOS Page").
Get(service+"/TOS.md").
Send().
ExpectHeader("Content-Type", "text/html; charset=UTF-8").
ExpectStatus(200).
ExpectContent("Terms of Service")
frisby.Create("Test /new page").
Get(service+"/new").
Send().
ExpectHeader("Content-Type", "text/html; charset=UTF-8").
ExpectStatus(200).
ExpectContent("Publish Note")
frisby.Create("Test non-existing page").
Get(service+"/xxyyzz").
Send().
ExpectStatus(404).
ExpectHeader("Content-Type", "text/plain; charset=UTF-8").
ExpectContent("Not found")
frisby.Create("Test non-existing page: query params").
Get(service + "/xxyyzz?q=v"). // TODO: test the same for valid note
Send().
ExpectStatus(404).
ExpectContent("Not found")
frisby.Create("Test non-existing page: alphabet violation").
Get(service + "/login.php").
Send().
ExpectStatus(404).
ExpectContent("Not found")
frisby.Create("Test publishing: no input").
Post(service+"/").
Send().
ExpectStatus(412).
ExpectJson("Success", false).
ExpectJson("Payload", "Precondition failed")
frisby.Create("Test publishing attempt: only TOS set").
Post(service+"/").
SetData("tos", "on").
Send().
ExpectStatus(400).
ExpectJson("Success", false).
ExpectJson("Payload", "Bad request: note length not accepted")
testNote := "# Hello World!\nThis is a _test_ note!"
testNoteHTML := "<h1>Hello World!</h1>\n<p>This is a <em>test</em> note!</p>"
var id string
tooLongNote := testNote
for len(tooLongNote) < 50000 {
tooLongNote += tooLongNote
}
frisby.Create("Test publishing: too long note").
Post(service+"/").
SetData("tos", "on").
SetData("text", tooLongNote).
Send().
ExpectStatus(400).
ExpectJson("Success", false).
ExpectJson("Payload", "Bad request: note length not accepted")
frisby.Create("Test publishing: correct inputs; no password").
Post(service+"/").
SetData("tos", "on").
SetData("text", testNote).
Send().
ExpectStatus(201).
ExpectJson("Success", true).
AfterJson(func(F *frisby.Frisby, json *simplejson.Json, err error) {
noteID, err := json.Get("Payload").String()
if err != nil {
F.AddError(err.Error())
return
}
id = noteID
})
frisby.Create("Test retrieval of new note").
Get(service + "/" + id).
Send().
// simulate 3 requests (for stats)
Get(service + "/" + id).
Send().
Get(service + "/" + id).
Send().
ExpectStatus(200).
ExpectContent(testNoteHTML)
frisby.Create("Test export of new note").
Get(service+"/"+id+"/export").
Send().
ExpectStatus(200).
ExpectHeader("Content-type", "text/plain; charset=UTF-8").
ExpectContent(testNote)
// TODO: fix this
// frisby.Create("Test opening fake service on note").
// Get(service + "/" + id + "/asd").
// Send().
// ExpectStatus(404).
// PrintBody().
// ExpectContent("Not found")
// frisby.Create("Test opening fake service on note 2").
// Get(service + "/" + id + "/exports").
// Send().
// ExpectStatus(404).
// ExpectContent("Not found")
frisby.Create("Test stats of new note").
Get(service + "/" + id + "/stats").
Send().
ExpectStatus(200).
ExpectContent("<tr><td>Views</td><td>4</td></tr>").
ExpectContent("Published")
frisby.Create("Test edit page of new note").
Get(service+"/"+id+"/edit").
Send().
ExpectStatus(200).
ExpectHeader("Content-type", "text/html; charset=UTF-8").
ExpectContent(testNote)
frisby.Create("Test invalid editing attempt: empty inputs").
Post(service+"/").
SetData("id", id).
Send().
ExpectStatus(412)
frisby.Create("Test invalid editing attempt: tos only").
Post(service+"/").
SetData("id", id).
SetData("tos", "on").
Send().
ExpectStatus(400).
ExpectJson("Success", false).
ExpectJson("Payload", "Bad request: password is empty")
frisby.Create("Test invalid editing attempt: tos and password").
Post(service+"/").
SetData("id", id).
SetData("tos", "on").
SetData("password", "aazzss").
Send().
ExpectStatus(401).
ExpectJson("Success", false).
ExpectJson("Payload", "Unauthorized: password is wrong")
frisby.Create("Test invalid editing attempt: tos and password, but too short note").
Post(service+"/").
SetData("id", id).
SetData("tos", "on").
SetData("text", "Test").
SetData("password", "aazzss").
Send().
ExpectStatus(400).
ExpectJson("Success", false).
ExpectJson("Payload", "Bad request: note length not accepted")
frisby.Create("Test publishing: correct inputs; with password").
Post(service+"/").
SetData("tos", "on").
SetData("password", "aa11qq").
SetData("text", testNote).
Send().
ExpectStatus(201).
ExpectJson("Success", true).
AfterJson(func(F *frisby.Frisby, json *simplejson.Json, err error) {
noteID, err := json.Get("Payload").String()
if err != nil {
F.AddError(err.Error())
return
}
id = noteID
})
updatedNote := strings.Replace(testNote, "is a", "is an updated", -1)
frisby.Create("Test invalid editing attempt: tos only").
Post(service+"/").
SetData("id", id).
SetData("tos", "on").
Send().
ExpectStatus(400).
ExpectJson("Success", false).
ExpectJson("Payload", "Bad request: password is empty")
frisby.Create("Test invalid editing attempt: tos and wrong password").
Post(service+"/").
SetData("id", id).
SetData("tos", "on").
SetData("text", updatedNote).
SetData("password", "aazzss").
Send().
ExpectStatus(401).
ExpectJson("Success", false).
ExpectJson("Payload", "Unauthorized: password is wrong")
frisby.Create("Test editing: valid inputs, no tos").
Post(service+"/").
SetData("id", id).
SetData("text", updatedNote).
SetData("password", "aa11qq").
Send().
ExpectStatus(412).
ExpectJson("Success", false).
ExpectJson("Payload", "Precondition failed")
frisby.Create("Test editing: valid inputs").
Post(service+"/").
SetData("id", id).
SetData("tos", "on").
SetData("text", updatedNote).
SetData("password", "aa11qq").
Send().
ExpectStatus(200).
ExpectJson("Success", true)
frisby.Create("Test retrieval of updated note").
Get(service + "/" + id).
Send().
ExpectStatus(200).
ExpectContent(strings.Replace(testNoteHTML, "is a", "is an updated", -1))
frisby.Create("Test export of new note").
Get(service+"/"+id+"/export").
Send().
ExpectStatus(200).
ExpectHeader("Content-type", "text/plain; charset=UTF-8").
ExpectContent(updatedNote)
frisby.Create("Test deletion: valid inputs").
Post(service+"/").
SetData("id", id).
SetData("tos", "on").
SetData("text", "").
SetData("password", "aa11qq").
Send().
ExpectStatus(200).
ExpectJson("Success", true)
frisby.Create("Test retrieval of deleted note").
Get(service + "/" + id).
Send().
ExpectStatus(404)
fraudNote := "http://n.co https://a.co ftp://b.co"
frisby.Create("Test publishing fraudulent note").
Post(service+"/").
SetData("tos", "on").
SetData("password", "aa22qq").
SetData("text", fraudNote).
Send().
ExpectStatus(201).
ExpectJson("Success", true).
AfterJson(func(F *frisby.Frisby, json *simplejson.Json, err error) {
noteID, err := json.Get("Payload").String()
if err != nil {
F.AddError(err.Error())
return
}
id = noteID
})
frisby.Create("Test new fraudulent note").
Get(service+"/"+id).
Send().
ExpectStatus(200).
ExpectHeader("Content-type", "text/html; charset=UTF-8").
ExpectContent(`<a href="http://n.co">http://n.co</a> <a href="https://a.co">https://a.co</a> <a href="ftp://b.co">ftp://b.co</a>`)
frisby.Create("Test export of fraudulent note").
Get(service+"/"+id+"/export").
Send().
ExpectStatus(200).
ExpectHeader("Content-type", "text/plain; charset=UTF-8").
ExpectContent(fraudNote)
// access fraudulent note more than 100 times
f := frisby.Create("Test export of fraudulent note again")
for i := 0; i < 100; i++ {
f.Get(service + "/" + id).Send()
}
frisby.Create("Test stats of fradulent note").
Get(service + "/" + id + "/stats").
Send().
ExpectStatus(200).
ExpectContent("<tr><td>Views</td><td>102</td></tr>").
ExpectContent("Published")
frisby.Create("Test export of fraudulent note").
Get(service+"/"+id+"/export").
Send().
ExpectStatus(403).
ExpectHeader("Content-type", "text/plain; charset=UTF-8").
ExpectContent("Forbidden")
frisby.Create("Test deletion of fraudulent note: wrong password inputs").
Post(service+"/").
SetData("id", id).
SetData("tos", "on").
SetData("text", "").
SetData("password", "aa11qq").
Send().
ExpectStatus(401).
ExpectJson("Success", false)
frisby.Create("Test deletion of fraudulent note: correct password inputs").
Post(service+"/").
SetData("id", id).
SetData("tos", "on").
SetData("text", "").
SetData("password", "aa22qq").
Send().
ExpectStatus(200).
ExpectJson("Success", true)
frisby.Global.PrintReport()
}
Loading…
Cancel
Save