Unit testing #23
| @ -17,3 +17,7 @@ func (app *application) home(w http.ResponseWriter, r *http.Request) { | |||||||
| func (app *application) notImplemented(w http.ResponseWriter, r *http.Request) { | func (app *application) notImplemented(w http.ResponseWriter, r *http.Request) { | ||||||
| 	views.ComingSoon("Coming Soon", app.newCommonData(r)).Render(r.Context(), w) | 	views.ComingSoon("Coming Soon", app.newCommonData(r)).Render(r.Context(), w) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func ping(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	w.Write([]byte("OK")) | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										60
									
								
								cmd/web/handlers_guestbook_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								cmd/web/handlers_guestbook_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,60 @@ | |||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"git.32bit.cafe/32bitcafe/guestbook/internal/assert" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestPing(t *testing.T) { | ||||||
|  | 	app := newTestApplication(t) | ||||||
|  | 	ts := newTestServer(t, app.routes()) | ||||||
|  | 	defer ts.Close() | ||||||
|  | 
 | ||||||
|  | 	code, _, body := ts.get(t, "/ping") | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, code, http.StatusOK) | ||||||
|  | 	assert.Equal(t, body, "OK") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestGetGuestbookView(t *testing.T) { | ||||||
|  | 	app := newTestApplication(t) | ||||||
|  | 	ts := newTestServer(t, app.routes()) | ||||||
|  | 	defer ts.Close() | ||||||
|  | 
 | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name     string | ||||||
|  | 		urlPath  string | ||||||
|  | 		wantCode int | ||||||
|  | 		wantBody string | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:     "Valid id", | ||||||
|  | 			urlPath:  fmt.Sprintf("/websites/%s/guestbook", shortIdToSlug(1)), | ||||||
|  | 			wantCode: http.StatusOK, | ||||||
|  | 			wantBody: "Guestbook for Example", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "Non-existent ID", | ||||||
|  | 			urlPath:  fmt.Sprintf("/websites/%s/guestbook", shortIdToSlug(2)), | ||||||
|  | 			wantCode: http.StatusNotFound, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "String ID", | ||||||
|  | 			urlPath:  "/websites/abcd/guestbook", | ||||||
|  | 			wantCode: http.StatusNotFound, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			code, _, body := ts.get(t, tt.urlPath) | ||||||
|  | 			assert.Equal(t, code, tt.wantCode) | ||||||
|  | 			if tt.wantBody != "" { | ||||||
|  | 				assert.StringContains(t, body, tt.wantBody) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										121
									
								
								cmd/web/handlers_user_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								cmd/web/handlers_user_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,121 @@ | |||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"git.32bit.cafe/32bitcafe/guestbook/internal/assert" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestUserSignup(t *testing.T) { | ||||||
|  | 	app := newTestApplication(t) | ||||||
|  | 	ts := newTestServer(t, app.routes()) | ||||||
|  | 	defer ts.Close() | ||||||
|  | 
 | ||||||
|  | 	_, _, body := ts.get(t, "/users/register") | ||||||
|  | 	validCSRFToken := extractCSRFToken(t, body) | ||||||
|  | 
 | ||||||
|  | 	const ( | ||||||
|  | 		validName     = "John" | ||||||
|  | 		validPassword = "validPassword" | ||||||
|  | 		validEmail    = "john@example.com" | ||||||
|  | 		formTag       = `<form action="/users/register" method="post">` | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name         string | ||||||
|  | 		userName     string | ||||||
|  | 		userEmail    string | ||||||
|  | 		userPassword string | ||||||
|  | 		csrfToken    string | ||||||
|  | 		wantCode     int | ||||||
|  | 		wantFormTag  string | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:         "Valid submission", | ||||||
|  | 			userName:     validName, | ||||||
|  | 			userEmail:    validEmail, | ||||||
|  | 			userPassword: validPassword, | ||||||
|  | 			csrfToken:    validCSRFToken, | ||||||
|  | 			wantCode:     http.StatusSeeOther, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:         "Missing token", | ||||||
|  | 			userName:     validName, | ||||||
|  | 			userEmail:    validEmail, | ||||||
|  | 			userPassword: validPassword, | ||||||
|  | 			wantCode:     http.StatusBadRequest, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:         "Empty name", | ||||||
|  | 			userName:     "", | ||||||
|  | 			userEmail:    validEmail, | ||||||
|  | 			userPassword: validPassword, | ||||||
|  | 			csrfToken:    validCSRFToken, | ||||||
|  | 			wantCode:     http.StatusUnprocessableEntity, | ||||||
|  | 			wantFormTag:  formTag, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:         "Empty email", | ||||||
|  | 			userName:     validName, | ||||||
|  | 			userEmail:    "", | ||||||
|  | 			userPassword: validPassword, | ||||||
|  | 			csrfToken:    validCSRFToken, | ||||||
|  | 			wantCode:     http.StatusUnprocessableEntity, | ||||||
|  | 			wantFormTag:  formTag, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:         "Empty password", | ||||||
|  | 			userName:     validName, | ||||||
|  | 			userEmail:    validEmail, | ||||||
|  | 			userPassword: "", | ||||||
|  | 			csrfToken:    validCSRFToken, | ||||||
|  | 			wantCode:     http.StatusUnprocessableEntity, | ||||||
|  | 			wantFormTag:  formTag, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:         "Invalid email", | ||||||
|  | 			userName:     validName, | ||||||
|  | 			userEmail:    "asdfasdf", | ||||||
|  | 			userPassword: validPassword, | ||||||
|  | 			csrfToken:    validCSRFToken, | ||||||
|  | 			wantCode:     http.StatusUnprocessableEntity, | ||||||
|  | 			wantFormTag:  formTag, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:         "Invalid password", | ||||||
|  | 			userName:     validName, | ||||||
|  | 			userEmail:    validEmail, | ||||||
|  | 			userPassword: "asdfasd", | ||||||
|  | 			csrfToken:    validCSRFToken, | ||||||
|  | 			wantCode:     http.StatusUnprocessableEntity, | ||||||
|  | 			wantFormTag:  formTag, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:         "Duplicate email", | ||||||
|  | 			userName:     validName, | ||||||
|  | 			userEmail:    "dupe@example.com", | ||||||
|  | 			userPassword: validPassword, | ||||||
|  | 			csrfToken:    validCSRFToken, | ||||||
|  | 			wantCode:     http.StatusUnprocessableEntity, | ||||||
|  | 			wantFormTag:  formTag, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(*testing.T) { | ||||||
|  | 			form := url.Values{} | ||||||
|  | 			form.Add("username", tt.userName) | ||||||
|  | 			form.Add("email", tt.userEmail) | ||||||
|  | 			form.Add("password", tt.userPassword) | ||||||
|  | 			form.Add("csrf_token", tt.csrfToken) | ||||||
|  | 			code, _, body := ts.postForm(t, "/users/register", form) | ||||||
|  | 			assert.Equal(t, code, tt.wantCode) | ||||||
|  | 			if tt.wantFormTag != "" { | ||||||
|  | 				assert.StringContains(t, body, tt.wantFormTag) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -21,9 +21,9 @@ import ( | |||||||
| type application struct { | type application struct { | ||||||
| 	sequence          uint16 | 	sequence          uint16 | ||||||
| 	logger            *slog.Logger | 	logger            *slog.Logger | ||||||
| 	websites          *models.WebsiteModel | 	websites          models.WebsiteModelInterface | ||||||
| 	users             *models.UserModel | 	users             models.UserModelInterface | ||||||
| 	guestbookComments *models.GuestbookCommentModel | 	guestbookComments models.GuestbookCommentModelInterface | ||||||
| 	sessionManager    *scs.SessionManager | 	sessionManager    *scs.SessionManager | ||||||
| 	formDecoder       *schema.Decoder | 	formDecoder       *schema.Decoder | ||||||
| 	debug             bool | 	debug             bool | ||||||
|  | |||||||
| @ -11,6 +11,8 @@ func (app *application) routes() http.Handler { | |||||||
| 	mux := http.NewServeMux() | 	mux := http.NewServeMux() | ||||||
| 	mux.Handle("GET /static/", http.FileServerFS(ui.Files)) | 	mux.Handle("GET /static/", http.FileServerFS(ui.Files)) | ||||||
| 
 | 
 | ||||||
|  | 	mux.HandleFunc("GET /ping", ping) | ||||||
|  | 
 | ||||||
| 	dynamic := alice.New(app.sessionManager.LoadAndSave, noSurf, app.authenticate) | 	dynamic := alice.New(app.sessionManager.LoadAndSave, noSurf, app.authenticate) | ||||||
| 	standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders) | 	standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders) | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										96
									
								
								cmd/web/testutils_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								cmd/web/testutils_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,96 @@ | |||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"html" | ||||||
|  | 	"io" | ||||||
|  | 	"log/slog" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/http/cookiejar" | ||||||
|  | 	"net/http/httptest" | ||||||
|  | 	"net/url" | ||||||
|  | 	"regexp" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"git.32bit.cafe/32bitcafe/guestbook/internal/models/mocks" | ||||||
|  | 	"github.com/alexedwards/scs/v2" | ||||||
|  | 	"github.com/gorilla/schema" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func newTestApplication(t *testing.T) *application { | ||||||
|  | 	formDecoder := schema.NewDecoder() | ||||||
|  | 	formDecoder.IgnoreUnknownKeys(true) | ||||||
|  | 
 | ||||||
|  | 	sessionManager := scs.New() | ||||||
|  | 	sessionManager.Lifetime = 12 * time.Hour | ||||||
|  | 	sessionManager.Cookie.Secure = true | ||||||
|  | 
 | ||||||
|  | 	return &application{ | ||||||
|  | 		logger:            slog.New(slog.NewTextHandler(io.Discard, nil)), | ||||||
|  | 		sessionManager:    sessionManager, | ||||||
|  | 		websites:          &mocks.WebsiteModel{}, | ||||||
|  | 		users:             &mocks.UserModel{}, | ||||||
|  | 		guestbookComments: &mocks.GuestbookCommentModel{}, | ||||||
|  | 		formDecoder:       formDecoder, | ||||||
|  | 		timezones:         getAvailableTimezones(), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type testServer struct { | ||||||
|  | 	*httptest.Server | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newTestServer(t *testing.T, h http.Handler) *testServer { | ||||||
|  | 	ts := httptest.NewTLSServer(h) | ||||||
|  | 	jar, err := cookiejar.New(nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	ts.Client().Jar = jar | ||||||
|  | 	ts.Client().CheckRedirect = func(req *http.Request, via []*http.Request) error { | ||||||
|  | 		return http.ErrUseLastResponse | ||||||
|  | 	} | ||||||
|  | 	return &testServer{ts} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ts *testServer) get(t *testing.T, urlPath string) (int, http.Header, string) { | ||||||
|  | 	rs, err := ts.Client().Get(ts.URL + urlPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	defer rs.Body.Close() | ||||||
|  | 	body, err := io.ReadAll(rs.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	body = bytes.TrimSpace(body) | ||||||
|  | 
 | ||||||
|  | 	return rs.StatusCode, rs.Header, string(body) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ts *testServer) postForm(t *testing.T, urlPath string, form url.Values) (int, http.Header, string) { | ||||||
|  | 	rs, err := ts.Client().PostForm(ts.URL+urlPath, form) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	defer rs.Body.Close() | ||||||
|  | 	body, err := io.ReadAll(rs.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	body = bytes.TrimSpace(body) | ||||||
|  | 	return rs.StatusCode, rs.Header, string(body) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var csrfTokenRX = regexp.MustCompile(`<input type="hidden" name="csrf_token" value="(.+?)">`) | ||||||
|  | 
 | ||||||
|  | func extractCSRFToken(t *testing.T, body string) string { | ||||||
|  | 	matches := csrfTokenRX.FindStringSubmatch(body) | ||||||
|  | 	if len(matches) < 2 { | ||||||
|  | 		t.Fatal("no csrf token found in body") | ||||||
|  | 	} | ||||||
|  | 	return html.UnescapeString(matches[1]) | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								internal/assert/assert.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								internal/assert/assert.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | package assert | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func Equal[T comparable](t *testing.T, actual, expected T) { | ||||||
|  | 	t.Helper() | ||||||
|  | 
 | ||||||
|  | 	if actual != expected { | ||||||
|  | 		t.Errorf("got: %v; want %v", actual, expected) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func StringContains(t *testing.T, actual, expectedSubstring string) { | ||||||
|  | 	t.Helper() | ||||||
|  | 
 | ||||||
|  | 	if !strings.Contains(actual, expectedSubstring) { | ||||||
|  | 		t.Errorf("got: %q; expected to contain: %q", actual, expectedSubstring) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -24,6 +24,15 @@ type GuestbookCommentModel struct { | |||||||
| 	DB *sql.DB | 	DB *sql.DB | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | type GuestbookCommentModelInterface interface { | ||||||
|  | 	Insert(shortId uint64, guestbookId, parentId int64, authorName, authorEmail, authorSite, commentText, pageUrl string, isPublished bool) (int64, error) | ||||||
|  | 	Get(shortId uint64) (GuestbookComment, error) | ||||||
|  | 	GetAll(guestbookId int64) ([]GuestbookComment, error) | ||||||
|  | 	GetDeleted(guestbookId int64) ([]GuestbookComment, error) | ||||||
|  | 	GetUnpublished(guestbookId int64) ([]GuestbookComment, error) | ||||||
|  | 	UpdateComment(comment *GuestbookComment) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (m *GuestbookCommentModel) Insert(shortId uint64, guestbookId, parentId int64, authorName, | func (m *GuestbookCommentModel) Insert(shortId uint64, guestbookId, parentId int64, authorName, | ||||||
| 	authorEmail, authorSite, commentText, pageUrl string, isPublished bool) (int64, error) { | 	authorEmail, authorSite, commentText, pageUrl string, isPublished bool) (int64, error) { | ||||||
| 	stmt := `INSERT INTO guestbook_comments (ShortId, GuestbookId, ParentId, AuthorName, | 	stmt := `INSERT INTO guestbook_comments (ShortId, GuestbookId, ParentId, AuthorName, | ||||||
|  | |||||||
							
								
								
									
										64
									
								
								internal/models/mocks/guestbookcomment.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								internal/models/mocks/guestbookcomment.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | |||||||
|  | package mocks | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"git.32bit.cafe/32bitcafe/guestbook/internal/models" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var mockGuestbookComment = models.GuestbookComment{ | ||||||
|  | 	ID:          1, | ||||||
|  | 	ShortId:     1, | ||||||
|  | 	GuestbookId: 1, | ||||||
|  | 	AuthorName:  "John Test", | ||||||
|  | 	AuthorEmail: "test@example.com", | ||||||
|  | 	AuthorSite:  "example.com", | ||||||
|  | 	CommentText: "Hello, world", | ||||||
|  | 	Created:     time.Now(), | ||||||
|  | 	IsPublished: true, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type GuestbookCommentModel struct{} | ||||||
|  | 
 | ||||||
|  | func (m *GuestbookCommentModel) Insert(shortId uint64, guestbookId, parentId int64, authorName, | ||||||
|  | 	authorEmail, authorSite, commentText, pageUrl string, isPublished bool) (int64, error) { | ||||||
|  | 	return 2, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *GuestbookCommentModel) Get(shortId uint64) (models.GuestbookComment, error) { | ||||||
|  | 	switch shortId { | ||||||
|  | 	case 1: | ||||||
|  | 		return mockGuestbookComment, nil | ||||||
|  | 	default: | ||||||
|  | 		return models.GuestbookComment{}, models.ErrNoRecord | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *GuestbookCommentModel) GetAll(guestbookId int64) ([]models.GuestbookComment, error) { | ||||||
|  | 	switch guestbookId { | ||||||
|  | 	case 1: | ||||||
|  | 		return []models.GuestbookComment{mockGuestbookComment}, nil | ||||||
|  | 	case 2: | ||||||
|  | 		return []models.GuestbookComment{}, nil | ||||||
|  | 	default: | ||||||
|  | 		return []models.GuestbookComment{}, models.ErrNoRecord | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *GuestbookCommentModel) GetDeleted(guestbookId int64) ([]models.GuestbookComment, error) { | ||||||
|  | 	switch guestbookId { | ||||||
|  | 	default: | ||||||
|  | 		return []models.GuestbookComment{}, models.ErrNoRecord | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *GuestbookCommentModel) GetUnpublished(guestbookId int64) ([]models.GuestbookComment, error) { | ||||||
|  | 	switch guestbookId { | ||||||
|  | 	default: | ||||||
|  | 		return []models.GuestbookComment{}, models.ErrNoRecord | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *GuestbookCommentModel) UpdateComment(comment *models.GuestbookComment) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										89
									
								
								internal/models/mocks/users.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								internal/models/mocks/users.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | |||||||
|  | package mocks | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"git.32bit.cafe/32bitcafe/guestbook/internal/models" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var mockUser = models.User{ | ||||||
|  | 	ID:       1, | ||||||
|  | 	ShortId:  1, | ||||||
|  | 	Username: "tester", | ||||||
|  | 	Email:    "test@example.com", | ||||||
|  | 	Deleted:  false, | ||||||
|  | 	IsBanned: false, | ||||||
|  | 	Created:  time.Now(), | ||||||
|  | 	Settings: mockUserSettings, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var mockUserSettings = models.UserSettings{ | ||||||
|  | 	LocalTimezone: time.UTC, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type UserModel struct { | ||||||
|  | 	Settings map[string]models.Setting | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *UserModel) InitializeSettingsMap() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *UserModel) Insert(shortId uint64, username string, email string, password string, settings models.UserSettings) error { | ||||||
|  | 	switch email { | ||||||
|  | 	case "dupe@example.com": | ||||||
|  | 		return models.ErrDuplicateEmail | ||||||
|  | 	default: | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *UserModel) Get(shortId uint64) (models.User, error) { | ||||||
|  | 	switch shortId { | ||||||
|  | 	case 1: | ||||||
|  | 		return mockUser, nil | ||||||
|  | 	default: | ||||||
|  | 		return models.User{}, models.ErrNoRecord | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *UserModel) GetById(id int64) (models.User, error) { | ||||||
|  | 	switch id { | ||||||
|  | 	case 1: | ||||||
|  | 		return mockUser, nil | ||||||
|  | 	default: | ||||||
|  | 		return models.User{}, models.ErrNoRecord | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *UserModel) GetAll() ([]models.User, error) { | ||||||
|  | 	return []models.User{mockUser}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *UserModel) Authenticate(email, password string) (int64, error) { | ||||||
|  | 	if email == "test@example.com" && password == "password" { | ||||||
|  | 		return 1, nil | ||||||
|  | 	} | ||||||
|  | 	return 0, models.ErrInvalidCredentials | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *UserModel) Exists(id int64) (bool, error) { | ||||||
|  | 	switch id { | ||||||
|  | 	case 1: | ||||||
|  | 		return true, nil | ||||||
|  | 	default: | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *UserModel) GetSettings(userId int64) (models.UserSettings, error) { | ||||||
|  | 	return mockUserSettings, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *UserModel) UpdateUserSettings(userId int64, settings models.UserSettings) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *UserModel) UpdateSetting(userId int64, setting models.Setting, value string) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										77
									
								
								internal/models/mocks/website.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								internal/models/mocks/website.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,77 @@ | |||||||
|  | package mocks | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"git.32bit.cafe/32bitcafe/guestbook/internal/models" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var mockGuestbook = models.Guestbook{ | ||||||
|  | 	ID:        1, | ||||||
|  | 	ShortId:   1, | ||||||
|  | 	UserId:    1, | ||||||
|  | 	WebsiteId: 1, | ||||||
|  | 	Created:   time.Now(), | ||||||
|  | 	IsActive:  true, | ||||||
|  | 	Settings: models.GuestbookSettings{ | ||||||
|  | 		IsCommentingEnabled:   true, | ||||||
|  | 		IsVisible:             true, | ||||||
|  | 		FilteredWords:         make([]string, 0), | ||||||
|  | 		AllowRemoteHostAccess: true, | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var mockWebsite = models.Website{ | ||||||
|  | 	ID:         1, | ||||||
|  | 	ShortId:    1, | ||||||
|  | 	Name:       "Example", | ||||||
|  | 	SiteUrl:    "example.com", | ||||||
|  | 	AuthorName: "John Test", | ||||||
|  | 	UserId:     1, | ||||||
|  | 	Created:    time.Now(), | ||||||
|  | 	Guestbook:  mockGuestbook, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type WebsiteModel struct{} | ||||||
|  | 
 | ||||||
|  | func (m *WebsiteModel) Insert(shortId uint64, userId int64, siteName, siteUrl, authorName string) (int64, error) { | ||||||
|  | 	return 2, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *WebsiteModel) Get(shortId uint64) (models.Website, error) { | ||||||
|  | 	switch shortId { | ||||||
|  | 	case 1: | ||||||
|  | 		return mockWebsite, nil | ||||||
|  | 	default: | ||||||
|  | 		return models.Website{}, models.ErrNoRecord | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *WebsiteModel) GetAllUser(userId int64) ([]models.Website, error) { | ||||||
|  | 	return []models.Website{mockWebsite}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *WebsiteModel) GetById(id int64) (models.Website, error) { | ||||||
|  | 	switch id { | ||||||
|  | 	case 1: | ||||||
|  | 		return mockWebsite, nil | ||||||
|  | 	default: | ||||||
|  | 		return models.Website{}, models.ErrNoRecord | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *WebsiteModel) GetAll() ([]models.Website, error) { | ||||||
|  | 	return []models.Website{mockWebsite}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *WebsiteModel) InitializeSettingsMap() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *WebsiteModel) UpdateGuestbookSettings(guestbookId int64, settings models.GuestbookSettings) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *WebsiteModel) UpdateSetting(guestbookId int64, setting models.Setting, value string) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
| @ -35,6 +35,18 @@ type UserModel struct { | |||||||
| 	Settings map[string]Setting | 	Settings map[string]Setting | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | type UserModelInterface interface { | ||||||
|  | 	InitializeSettingsMap() error | ||||||
|  | 	Insert(shortId uint64, username string, email string, password string, settings UserSettings) error | ||||||
|  | 	Get(shortId uint64) (User, error) | ||||||
|  | 	GetById(id int64) (User, error) | ||||||
|  | 	GetAll() ([]User, error) | ||||||
|  | 	Authenticate(email, password string) (int64, error) | ||||||
|  | 	Exists(id int64) (bool, error) | ||||||
|  | 	UpdateUserSettings(userId int64, settings UserSettings) error | ||||||
|  | 	UpdateSetting(userId int64, setting Setting, value string) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (m *UserModel) InitializeSettingsMap() error { | func (m *UserModel) InitializeSettingsMap() error { | ||||||
| 	if m.Settings == nil { | 	if m.Settings == nil { | ||||||
| 		m.Settings = make(map[string]Setting) | 		m.Settings = make(map[string]Setting) | ||||||
|  | |||||||
| @ -90,6 +90,16 @@ func (m *WebsiteModel) InitializeSettingsMap() error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | type WebsiteModelInterface interface { | ||||||
|  | 	Insert(shortId uint64, userId int64, siteName, siteUrl, authorName string) (int64, error) | ||||||
|  | 	Get(shortId uint64) (Website, error) | ||||||
|  | 	GetAllUser(userId int64) ([]Website, error) | ||||||
|  | 	GetAll() ([]Website, error) | ||||||
|  | 	InitializeSettingsMap() error | ||||||
|  | 	UpdateGuestbookSettings(guestbookId int64, settings GuestbookSettings) error | ||||||
|  | 	UpdateSetting(guestbookId int64, setting Setting, value string) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (m *WebsiteModel) Insert(shortId uint64, userId int64, siteName, siteUrl, authorName string) (int64, error) { | func (m *WebsiteModel) Insert(shortId uint64, userId int64, siteName, siteUrl, authorName string) (int64, error) { | ||||||
| 	stmt := `INSERT INTO websites (ShortId, Name, SiteUrl, AuthorName, UserId, Created) | 	stmt := `INSERT INTO websites (ShortId, Name, SiteUrl, AuthorName, UserId, Created) | ||||||
| 			VALUES (?, ?, ?, ?, ?, ?)` | 			VALUES (?, ?, ?, ?, ?, ?)` | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user