Skip to content

Commit

Permalink
refectored
Browse files Browse the repository at this point in the history
  • Loading branch information
mparaiso committed Jul 9, 2016
1 parent aa14ae5 commit 143b000
Show file tree
Hide file tree
Showing 40 changed files with 493 additions and 218 deletions.
88 changes: 82 additions & 6 deletions app_test.go
@@ -1,6 +1,6 @@
// @Copyright (c) 2016 mparaiso <mparaiso@online.fr> All rights reserved.

package gonews_test
package main_test

import (
"database/sql"
Expand All @@ -11,12 +11,11 @@ import (
"net/http/httptest"
"os"
"path"

"testing"

"github.com/PuerkitoBio/goquery"
_ "github.com/mattn/go-sqlite3"
"github.com/mparaiso/go-news"
"github.com/mparaiso/go-news/internal"
"github.com/rubenv/sql-migrate"

"net/url"
Expand Down Expand Up @@ -151,6 +150,19 @@ func TestAppLogin_GET(t *testing.T) {
}
}

// TestAppLogin_POST logs a registered user into the application
func TestAppLogin_POST(t *testing.T) {
_, _, _, err := LoginUserHelper(t)
if err != nil {
t.Fatal(err)
}
}

func TestAppLogout(t *testing.T) {
// db, server, user, err := LoginUserHelper(t)

}

// TestAppLogin_POST_registration tests the registration process and verifies
// the new user has been persisted into the db
func TestAppLogin_POST_registration(t *testing.T) {
Expand Down Expand Up @@ -183,7 +195,7 @@ func TestAppLogin_POST_registration(t *testing.T) {
"registration_email": {"jefferson@acme.com"},
})
resp, err = http.Post(server.URL+"/register", "application/x-www-form-urlencoded", strings.NewReader(values.Encode()))
defer resp.Body.Close()
// defer resp.Body.Close()
if err != nil {
t.Fatal(err)
}
Expand All @@ -203,6 +215,70 @@ func TestAppLogin_POST_registration(t *testing.T) {
///http.CookieJar
}

// LoginUserHelper logs a user before executing a test
func LoginUserHelper(t *testing.T) (*sql.DB, *httptest.Server, *gonews.User, error) {
// Setup
db := GetDB(t)
server := SetUp(t, db)
unencryptedPassword := "password"
user := &gonews.User{Username: "mike_doe", Email: "mike_doe@acme.com"}
user.CreateSecurePassword(unencryptedPassword)
result, err := db.Exec("INSERT INTO users(username,email,password) values(?,?,?);", user.Username, user.Email, user.Password)
if err != nil {
t.Fatal(err)
}
// t.Logf("%#v", user)
if n, err := result.RowsAffected(); err != nil || n != 1 {
t.Fatal(n, err)
}
defer server.Close()
http.DefaultClient.Jar = NewTestCookieJar()
// test
res, err := http.Get(server.URL + "/login")
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
doc, err := goquery.NewDocumentFromResponse(res)
if err != nil {
t.Fatal(err)
}
selection := doc.Find("input[name='login_csrf']")

csrf, ok := selection.First().Attr("value")
if !ok {
t.Fatal("csrf not found in HTML document", selection, ok)
}
if strings.Trim(csrf, " ") == "" {
t.Fatal("csrf not found")
}
formValues := url.Values{
"login_username": {user.Username},
"login_password": {unencryptedPassword},
"login_csrf": {csrf},
}
res, err = http.Post(server.URL+"/login", "application/x-www-form-urlencoded", strings.NewReader(formValues.Encode()))

if err != nil {
t.Fatal(err)
}

if expected, got := 200, res.StatusCode; expected != got {
//t.Logf(" %s %s", ioutil.ReadAll(res.Body))
t.Fatalf("POST /login status : expected '%v' got '%v'", expected, got)
}
doc, err = goquery.NewDocumentFromResponse(res)
if err != nil {
t.Fatal(err)
}
selection = doc.Find(".current-user")
t.Log(doc.Html())
if expected, got := 1, selection.Length(); expected != got {
t.Fatalf(".current-user length : expect '%v' got '%v' ", expected, got)
}
return db, server, user, err
}

func TestApp_404(t *testing.T) {
server := SetUp(t)
defer server.Close()
Expand Down Expand Up @@ -238,7 +314,7 @@ var Directory = func() string {
return dir
}()

var MigrationDirectory = path.Join(Directory, "cmd", "go-news", "migrations", "development", "sqlite3")
var MigrationDirectory = path.Join(Directory, "migrations", "development", "sqlite3")

func GetDB(t *testing.T) *sql.DB {
db, err := sql.Open("sqlite3", ":memory:")
Expand All @@ -258,7 +334,7 @@ func MigrateUp(db *sql.DB) *sql.DB {
func TestingGetOptions(db *sql.DB) gonews.ContainerOptions {
options := gonews.DefaultContainerOptions()
options.Debug = DEBUG
options.TemplateDirectory = path.Join(Directory, "cmd", "go-news", options.TemplateDirectory)
options.TemplateDirectory = path.Join(Directory, options.TemplateDirectory)
options.ConnectionFactory = func() (*sql.DB, error) {
return db, nil
}
Expand Down
Binary file removed cmd/go-news/go-news.exe~
Binary file not shown.
File renamed without changes.
File renamed without changes.
12 changes: 0 additions & 12 deletions interfaces.go

This file was deleted.

19 changes: 12 additions & 7 deletions app.go → internal/app.go
Expand Up @@ -24,6 +24,9 @@ var DefaultContainerOptions = func() func() ContainerOptions {
return func() ContainerOptions {
return ContainerOptions{
Debug: true,
Title: "gonews",
Slogan: "the news site for gophers",
Description: "gonews is a site where gophers publish and discuss news about the go language",
DataSource: "db.sqlite3",
Driver: "sqlite3",
TemplateDirectory: "templates",
Expand Down Expand Up @@ -63,23 +66,25 @@ func GetApp(options ContainerOptions, appOptions AppOptions) http.Handler {
}
DefaultStack := &Stack{
Middlewares: []Middleware{
StopWatchMiddleware,
LoggingMiddleware,
SessionMiddleware,
CSRFMiddleWare,
TemplateMiddleware,
StopWatchMiddleware, // Benchmarks the stack execution time
LoggingMiddleware, // Logs each request formatted by the common log format
SessionMiddleware, // Initialize the session
CSRFMiddleWare, // Initiliaze the CSRF functionality
RefreshUserMiddleware, // Refresh an authenticated user if user_id exists in session
TemplateMiddleware, // Configures templates environment
}, ContainerFactory: containerFactory}
// A middleware stack with request logging
Default := DefaultStack.Build()
// A middleware stack that extends Zero and handles requests for missing pages
Home := DefaultStack.Clone().Push(NotFoundMiddleware).Build()
app := &App{http.NewServeMux()}
// homepage
app.HandleFunc("/", Home(ThreadIndexController))
app.HandleFunc("/", Default(NotFoundMiddleware, ThreadIndexController))
// thread
app.HandleFunc("/thread", Default(ThreadShowController))
// login
app.HandleFunc("/login", Default(LoginController))
// logout
app.HandleFunc("/logout", Default(PostOnlyMiddleware, LogoutController))
// user
app.HandleFunc("/user", Default(UserShowController))
// submitted user stories
Expand Down
26 changes: 26 additions & 0 deletions container.go → internal/container.go
Expand Up @@ -27,12 +27,16 @@ type ContainerOptions struct {
DataSource,
Driver,
Secret,
Title,
Slogan,
Description,
TemplateDirectory string
Debug bool
SessionStoreFactory func() (sessions.Store, error)
ConnectionFactory func() (*sql.DB, error)
LoggerFactory func() (LoggerInterface, error)
csrfProvider CSRFProvider
user *User
}

// Container contains all the application dependencies
Expand All @@ -46,6 +50,22 @@ type Container struct {
session SessionInterface
}

// HasAuthenticatedUser returns true if a user has been authenticated
func (c *Container) HasAuthenticatedUser() bool {
return c.user != nil
}

// SetCurrentUser sets the authenticated user
func (c *Container) SetCurrentUser(u *User) {
c.user = u
}

// CurrentUser returns an authenticated user
func (c *Container) CurrentUser() *User {
return c.user
}

// GetSecret returns the secret key
func (c *Container) GetSecret() string {
return c.ContainerOptions.Secret
}
Expand Down Expand Up @@ -113,6 +133,7 @@ func (c *Container) GetThreadRepository() (*ThreadRepository, error) {
return c.threadRepository, nil
}

// MustGetThreadRepository panics on error
func (c *Container) MustGetThreadRepository() *ThreadRepository {
r, err := c.GetThreadRepository()
if err != nil {
Expand Down Expand Up @@ -153,6 +174,11 @@ func (c *Container) GetCSRFProvider(request *http.Request) CSRFProvider {
return c.csrfProvider
}

// GetOptions returns the container's options
func (c *Container) GetOptions() ContainerOptions {
return c.ContainerOptions
}

// GetLogger gets a logger
func (c *Container) GetLogger() (LoggerInterface, error) {
if c.logger == nil {
Expand Down
106 changes: 82 additions & 24 deletions controllers.go → internal/controllers.go
Expand Up @@ -11,21 +11,22 @@ import (

// ThreadIndexController displays a list of links
func ThreadIndexController(c *Container, rw http.ResponseWriter, r *http.Request, next func()) {
repository, err := c.GetThreadRepository()
if err != nil {
c.HTTPError(rw, r, 500, err)
return
}
threads, err := repository.GetThreadsOrderedByVoteCount(100, 0)
if err != nil {
c.HTTPError(rw, r, 500, err)
return
}
var threads Threads

err = c.MustGetTemplate().ExecuteTemplate(rw, "thread_list.tpl.html", map[string]interface{}{"Threads": threads})
if err != nil {
c.HTTPError(rw, r, 500, err)
}
repository, err := c.GetThreadRepository()
if err == nil {
threads, err = repository.GetThreadsOrderedByVoteCount(100, 0)
if err == nil {
err = c.MustGetTemplate().ExecuteTemplate(rw, "thread_list.tpl.html", map[string]interface{}{
"Threads": threads,
"Title": "homepage",
})
if err == nil {
return
}
}
}
c.HTTPError(rw, r, 500, err)
}

// ThreadListByAuthorIDController displays user's submitted stories
Expand Down Expand Up @@ -86,21 +87,78 @@ func ThreadShowController(c *Container, rw http.ResponseWriter, r *http.Request,
}
}

// LogoutController logs out a user
func LogoutController(c *Container, rw http.ResponseWriter, r *http.Request, next func()) {
c.MustGetSession(r).Set("user_id", nil)
c.SetCurrentUser(nil)
http.Redirect(rw, r, "/", http.StatusOK)
}

// LoginController displays the login/signup page
func LoginController(c *Container, rw http.ResponseWriter, r *http.Request, next func()) {
loginCSRF := c.GetCSRFProvider(r).Generate( r.RemoteAddr, "login")
loginForm := &LoginForm{CSRF: loginCSRF, Name: "login"}
registrationCSRF := c.GetCSRFProvider(r).Generate( r.RemoteAddr, "registration")
registrationForm := &RegistrationForm{CSRF: registrationCSRF, Name: "registration"}
err := c.MustGetTemplate().ExecuteTemplate(rw, "login.tpl.html", map[string]interface{}{
"LoginForm": loginForm,
"RegistrationForm": registrationForm,
})
if err != nil {
c.HTTPError(rw, r, 500, err)
switch r.Method {
case "GET":
loginCSRF := c.GetCSRFProvider(r).Generate(r.RemoteAddr, "login")
loginForm := &LoginForm{CSRF: loginCSRF, Name: "login"}
registrationCSRF := c.GetCSRFProvider(r).Generate(r.RemoteAddr, "registration")
registrationForm := &RegistrationForm{CSRF: registrationCSRF, Name: "registration"}
err := c.MustGetTemplate().ExecuteTemplate(rw, "login.tpl.html", map[string]interface{}{
"LoginForm": loginForm,
"RegistrationForm": registrationForm,
})
if err != nil {
c.HTTPError(rw, r, 500, err)
}
return
case "POST":
var loginErrorMessage string
var candidate *User
err := r.ParseForm()
if err != nil {
c.HTTPError(rw, r, 500, err)
return
}
loginForm := &LoginForm{}
err = loginForm.HandleRequest(r)
if err != nil {
c.HTTPError(rw, r, 500, err)
return
}
loginFormValidator := &LoginFormValidator{c.GetCSRFProvider(r), r}
err = loginFormValidator.Validate(loginForm)
// authenticate user
if err == nil {
user := loginForm.Model()
userRepository := c.MustGetUserRepository()
candidate, err = userRepository.GetOneByUsername(user.Username)
if err == nil && candidate != nil {
err = candidate.Authenticate(user.Password)
if err == nil {
// authenticated
c.MustGetSession(r).Set("user_id", candidate.ID)
http.Redirect(rw, r, "/", http.StatusOK)
return
}
} else if candidate == nil {
loginErrorMessage = "Invalid Credentials"
}
}
rw.WriteHeader(http.StatusBadRequest)
registrationCSRF := c.GetCSRFProvider(r).Generate(r.RemoteAddr, "registration")
registrationForm := &RegistrationForm{CSRF: registrationCSRF, Name: "registration"}
err = c.MustGetTemplate().ExecuteTemplate(rw, "login.tpl.html", map[string]interface{}{
"LoginForm": loginForm,
"RegistrationForm": registrationForm,
"LoginErrorMessage": loginErrorMessage,
})
return

default:
c.HTTPError(rw, r, http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
}

// RegistrationController handles user registration
func RegistrationController(c *Container, rw http.ResponseWriter, r *http.Request, next func()) {
// Parse form
err := r.ParseForm()
Expand Down

0 comments on commit 143b000

Please sign in to comment.