mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-08-26 10:36:23 -07:00
Add spoilered column and migration, update tests in gctemplates to use goquery to not have to test against really big strings
This commit is contained in:
parent
052a75da28
commit
3dc45fef53
12 changed files with 322 additions and 299 deletions
|
@ -193,5 +193,17 @@ func updateMysqlDB(ctx context.Context, dbu *GCDatabaseUpdater, sqlConfig *confi
|
|||
}
|
||||
}
|
||||
|
||||
// add spoilered column to DBPREFIXthreads
|
||||
dataType, err = common.ColumnType(ctx, db, nil, "spoilered", "DBPREFIXthreads", sqlConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dataType == "" {
|
||||
query = `ALTER TABLE DBPREFIXthreads ADD COLUMN spoilered BOOL NOT NULL DEFAULT FALSE`
|
||||
if _, err = db.ExecContextSQL(ctx, nil, query); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -35,21 +35,31 @@ type migrationPost struct {
|
|||
func (*Pre2021Migrator) migratePost(tx *sql.Tx, post *migrationPost, errEv *zerolog.Event) error {
|
||||
var err error
|
||||
opts := &gcsql.RequestOptions{Tx: tx}
|
||||
thread := &gcsql.Thread{
|
||||
ID: post.ThreadID,
|
||||
BoardID: post.boardID,
|
||||
Locked: post.locked,
|
||||
Stickied: post.stickied,
|
||||
Anchored: post.autosage,
|
||||
Cyclic: false,
|
||||
}
|
||||
if post.oldParentID == 0 {
|
||||
// migrating post was a thread OP, create the row in the threads table
|
||||
if post.ThreadID, err = gcsql.CreateThread(opts, post.boardID, false, post.stickied, post.autosage, false); err != nil {
|
||||
if err = gcsql.CreateThread(opts, thread); err != nil {
|
||||
errEv.Err(err).Caller().
|
||||
Int("boardID", post.boardID).
|
||||
Msg("Failed to create thread")
|
||||
}
|
||||
post.ThreadID = thread.ID
|
||||
}
|
||||
|
||||
// insert thread top post
|
||||
if err = post.Insert(true, post.boardID, false, post.stickied, post.autosage, false, opts); err != nil {
|
||||
if err = post.Insert(true, thread, true, opts); err != nil {
|
||||
errEv.Err(err).Caller().
|
||||
Int("boardID", post.boardID).
|
||||
Int("threadID", post.ThreadID).
|
||||
Msg("Failed to insert thread OP")
|
||||
return err
|
||||
}
|
||||
|
||||
if post.filename != "" {
|
||||
|
|
|
@ -79,11 +79,11 @@ func boardPagePathTmplFunc(board *gcsql.Board, page int) string {
|
|||
return config.WebPath(board.Dir, strconv.Itoa(page)+".html")
|
||||
}
|
||||
|
||||
func getBoardDefaultStyleTmplFunc(dir string) string {
|
||||
func getBoardDefaultStyleTmplFunc(dir string) (string, error) {
|
||||
boardCfg := config.GetBoardConfig(dir)
|
||||
if !boardCfg.IsGlobal() {
|
||||
// /<board>/board.json exists, overriding the default them and theme set in SQL
|
||||
return boardCfg.DefaultStyle
|
||||
return boardCfg.DefaultStyle, nil
|
||||
}
|
||||
var defaultStyle string
|
||||
err := gcsql.QueryRowTimeoutSQL(nil, "SELECT default_style FROM DBPREFIXboards WHERE dir = ?",
|
||||
|
@ -92,9 +92,9 @@ func getBoardDefaultStyleTmplFunc(dir string) string {
|
|||
gcutil.LogError(err).Caller().
|
||||
Str("board", dir).
|
||||
Msg("Unable to get default style attribute of board")
|
||||
return boardCfg.DefaultStyle
|
||||
return boardCfg.DefaultStyle, err
|
||||
}
|
||||
return defaultStyle
|
||||
return defaultStyle, nil
|
||||
}
|
||||
|
||||
func sectionBoardsTmplFunc(sectionID int) []gcsql.Board {
|
||||
|
|
|
@ -353,6 +353,16 @@ func (p *Post) InCyclicThread() (bool, error) {
|
|||
return cyclic, err
|
||||
}
|
||||
|
||||
// InSpoileredThread returns true if the post is in a spoilered thread
|
||||
func (p *Post) InSpoileredThread() (bool, error) {
|
||||
var spoilered bool
|
||||
err := QueryRowTimeoutSQL(nil, "SELECT spoilered FROM DBPREFIXthreads WHERE id = ?", []any{p.ThreadID}, []any{&spoilered})
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return false, ErrThreadDoesNotExist
|
||||
}
|
||||
return spoilered, err
|
||||
}
|
||||
|
||||
// Delete sets the post as deleted and sets the deleted_at timestamp to the current time
|
||||
func (p *Post) Delete(requestOptions ...*RequestOptions) error {
|
||||
shouldCommit := len(requestOptions) == 0
|
||||
|
@ -391,8 +401,9 @@ func (p *Post) Delete(requestOptions ...*RequestOptions) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Insert inserts the post into the database with the optional given options
|
||||
func (p *Post) Insert(bumpThread bool, boardID int, locked bool, stickied bool, anchored bool, cyclical bool, requestOptions ...*RequestOptions) error {
|
||||
// Insert inserts the post into the database with the optional given options. If force is not true and
|
||||
// the thread is locked, it will return an error. Force should only be used for special cases (ex: migration)
|
||||
func (p *Post) Insert(bumpThread bool, thread *Thread, force bool, requestOptions ...*RequestOptions) error {
|
||||
opts := setupOptions(requestOptions...)
|
||||
if len(requestOptions) == 0 {
|
||||
opts.Context, opts.Cancel = context.WithTimeout(context.Background(), gcdb.defaultTimeout)
|
||||
|
@ -421,19 +432,20 @@ func (p *Post) Insert(bumpThread bool, boardID int, locked bool, stickied bool,
|
|||
// thread doesn't exist yet, this is a new post
|
||||
p.IsTopPost = true
|
||||
var threadID int
|
||||
threadID, err = CreateThread(opts, boardID, locked, stickied, anchored, cyclical)
|
||||
if err != nil {
|
||||
if err = CreateThread(opts, thread); err != nil {
|
||||
return err
|
||||
}
|
||||
p.ThreadID = threadID
|
||||
} else {
|
||||
var threadIsLocked bool
|
||||
if err = QueryRow(opts, "SELECT locked FROM DBPREFIXthreads WHERE id = ?",
|
||||
[]any{p.ThreadID}, []any{&threadIsLocked}); err != nil {
|
||||
return err
|
||||
}
|
||||
if threadIsLocked {
|
||||
return ErrThreadLocked
|
||||
if !force {
|
||||
var threadIsLocked bool
|
||||
if err = QueryRow(opts, "SELECT locked FROM DBPREFIXthreads WHERE id = ?",
|
||||
[]any{p.ThreadID}, []any{&threadIsLocked}); err != nil {
|
||||
return err
|
||||
}
|
||||
if threadIsLocked {
|
||||
return ErrThreadLocked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -80,11 +80,11 @@ func createThreadTestRun(t *testing.T, driver string) {
|
|||
mock.ExpectPrepare(query).ExpectQuery().
|
||||
WithArgs(1).WillReturnRows(mock.NewRows([]string{"locked"}).AddRow(false))
|
||||
|
||||
threadID, err := CreateThread(nil, 1, false, false, false, false)
|
||||
if !assert.NoError(t, err) {
|
||||
thread := &Thread{BoardID: 1}
|
||||
if !assert.NoError(t, CreateThread(nil, thread)) {
|
||||
t.FailNow()
|
||||
}
|
||||
p := Post{ThreadID: threadID, Message: "test", MessageRaw: "test", IP: "192.168.56.1", IsTopPost: true, CreatedOn: time.Now()}
|
||||
p := Post{ThreadID: thread.ID, Message: "test", MessageRaw: "test", IP: "192.168.56.1", IsTopPost: true, CreatedOn: time.Now()}
|
||||
|
||||
if driver == "mysql" {
|
||||
query = insertIntoPostsMySQL
|
||||
|
@ -103,9 +103,9 @@ func createThreadTestRun(t *testing.T, driver string) {
|
|||
query = `UPDATE threads SET last_bump = CURRENT_TIMESTAMP WHERE id = \$1`
|
||||
}
|
||||
mock.ExpectPrepare(query).ExpectExec().
|
||||
WithArgs(threadID).WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
WithArgs(thread.ID).WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectCommit()
|
||||
if !assert.NoError(t, p.Insert(true, 1, false, false, false, false)) {
|
||||
if !assert.NoError(t, p.Insert(true, thread, false)) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.NoError(t, mock.ExpectationsWereMet())
|
||||
|
|
|
@ -280,6 +280,7 @@ type Thread struct {
|
|||
Stickied bool // sql: stickied
|
||||
Anchored bool // sql: anchored
|
||||
Cyclic bool // sql: cyclical
|
||||
Spoilered bool // sql: spoilered
|
||||
LastBump time.Time // sql: last_bump
|
||||
DeletedAt time.Time // sql: deleted_at
|
||||
IsDeleted bool // sql: is_deleted
|
||||
|
|
|
@ -20,23 +20,20 @@ var (
|
|||
)
|
||||
|
||||
// CreateThread creates a new thread in the database with the given board ID and statuses
|
||||
func CreateThread(requestOptions *RequestOptions, boardID int, locked bool, stickied bool, anchored bool, cyclic bool) (threadID int, err error) {
|
||||
func CreateThread(requestOptions *RequestOptions, thread *Thread) (err error) {
|
||||
const lockedQuery = `SELECT locked FROM DBPREFIXboards WHERE id = ?`
|
||||
const insertQuery = `INSERT INTO DBPREFIXthreads (board_id, locked, stickied, anchored, cyclical) VALUES (?,?,?,?,?)`
|
||||
var boardIsLocked bool
|
||||
if err = QueryRow(requestOptions, lockedQuery, []any{boardID}, []any{&boardIsLocked}); err != nil {
|
||||
return 0, err
|
||||
if err = QueryRow(requestOptions, lockedQuery, []any{&thread.BoardID}, []any{&boardIsLocked}); err != nil {
|
||||
return err
|
||||
}
|
||||
if boardIsLocked {
|
||||
return 0, ErrBoardIsLocked
|
||||
return ErrBoardIsLocked
|
||||
}
|
||||
if _, err = Exec(requestOptions, insertQuery, boardID, locked, stickied, anchored, cyclic); err != nil {
|
||||
return 0, err
|
||||
if _, err = Exec(requestOptions, insertQuery, &thread.BoardID, &thread.Locked, &thread.Stickied, &thread.Anchored, &thread.Cyclic); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = QueryRow(requestOptions, "SELECT MAX(id) FROM DBPREFIXthreads", nil, []any{&threadID}); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return threadID, nil
|
||||
return QueryRow(requestOptions, "SELECT MAX(id) FROM DBPREFIXthreads", nil, []any{&thread.ID})
|
||||
}
|
||||
|
||||
// GetThread returns a a thread object from the database, given its ID
|
||||
|
|
|
@ -2,43 +2,18 @@ package templatetests_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/gochan-org/gochan/pkg/building"
|
||||
"github.com/gochan-org/gochan/pkg/config"
|
||||
"github.com/gochan-org/gochan/pkg/gcsql"
|
||||
"github.com/gochan-org/gochan/pkg/server/serverutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
headBeginning = `<!DOCTYPE html><html lang="en"><head>` +
|
||||
`<meta charset="UTF-8"><meta name="viewport"content="width=device-width, initial-scale=1.0">`
|
||||
|
||||
headEndAndBodyStart = `<link rel="stylesheet"href="/css/global.css"/>` +
|
||||
`<link id="theme"rel="stylesheet"href="/css/pipes.css"/>` +
|
||||
`<link rel="shortcut icon"href="/favicon.png">` +
|
||||
`<script type="text/javascript"src="/js/consts.js"></script>` +
|
||||
`<script type="text/javascript"src="/js/gochan.js"defer></script></head>` +
|
||||
`<body><div id="topbar"><div class="topbar-section"><a href="/"class="topbar-item">home</a></div></div>` +
|
||||
`<header><h1 id="board-title">Gochan</h1></header>` +
|
||||
`<div id="content"><div class="section-block banpage-block">`
|
||||
|
||||
normalBanHeader = headBeginning + `<title>YOU ARE BANNED:(</title>` + headEndAndBodyStart +
|
||||
`<div class="section-title-block"><span class="section-title ban-title">YOU ARE BANNED:(</span></div>` +
|
||||
`<div class="section-body"><div id="ban-info">`
|
||||
|
||||
bannedForeverHeader = headBeginning + `<title>YOU'RE PERMABANNED, IDIOT!</title>` + headEndAndBodyStart +
|
||||
`<div class="section-title-block"><span class="section-title ban-title">YOU'RE PERMABANNED,IDIOT!</span></div>` +
|
||||
`<div class="section-body"><div id="ban-info">`
|
||||
|
||||
appealForm = `<form id="appeal-form"action="/post"method="POST">` +
|
||||
`<input type="hidden"name="board"value=""><input type="hidden"name="banid"value="0">` +
|
||||
`<textarea rows="4"cols="48"name="appealmsg"id="postmsg"placeholder="Appeal message"></textarea>` +
|
||||
`<input type="submit"name="doappeal"value="Submit"/><br/></form>`
|
||||
|
||||
footer = `<footer>Powered by<a href="http://github.com/gochan-org/gochan/">Gochan 4.0</a><br /></footer></div></body></html>`
|
||||
)
|
||||
|
||||
var (
|
||||
testingSiteConfig = &config.SiteConfig{
|
||||
SiteName: "Gochan",
|
||||
|
@ -72,18 +47,6 @@ var (
|
|||
AnonymousName: "Anonymous Coward",
|
||||
}
|
||||
|
||||
simpleBoard2 = &gcsql.Board{
|
||||
ID: 2,
|
||||
SectionID: 2,
|
||||
URI: "sup",
|
||||
Dir: "sup",
|
||||
Title: "Gochan Support board",
|
||||
Subtitle: "Board for helping out gochan users/admins",
|
||||
Description: "Board for helping out gochan users/admins",
|
||||
DefaultStyle: "yotsuba.css",
|
||||
AnonymousName: "Anonymous Coward",
|
||||
}
|
||||
|
||||
banPageCases = []templateTestCase{
|
||||
{
|
||||
desc: "appealable permaban",
|
||||
|
@ -107,12 +70,21 @@ var (
|
|||
DefaultStyle: "pipes.css",
|
||||
},
|
||||
},
|
||||
expectedOutput: normalBanHeader +
|
||||
`You are banned from posting on<span class="ban-boards">all boards</span>for the following reason:<p class="reason">ban message goes here</p>` +
|
||||
`Your ban was placed on<time datetime="0001-01-01T00:00:00Z"class="ban-timestamp">Mon,January 01,0001 12:00:00 AM</time> and will <span class="ban-timestamp">not expire</span>.<br/>` +
|
||||
`Your IP address is<span class="ban-ip">192.168.56.1</span>.<br /><br/>` +
|
||||
`You may appeal this ban:<br/>` + appealForm + `</div></div></div>` +
|
||||
footer,
|
||||
validationFunc: func(t *testing.T, reader io.Reader) {
|
||||
doc, err := goquery.NewDocumentFromReader(reader)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, "YOU ARE BANNED:(", doc.Find("title").Text())
|
||||
assert.Equal(t, "all boards", doc.Find(".ban-boards").Text())
|
||||
assert.Equal(t, "ban message goes here", doc.Find(".reason").Text())
|
||||
banTime := doc.Find(".ban-timestamp").First()
|
||||
assert.Equal(t, "0001-01-01T00:00:00Z", banTime.AttrOr("datetime", ""))
|
||||
assert.Equal(t, "Mon,January 01,0001 12:00:00 AM", banTime.Text())
|
||||
assert.Equal(t, "not expire", banTime.Next().Text())
|
||||
assert.Equal(t, "192.168.56.1", doc.Find(".ban-ip").Text())
|
||||
assert.Equal(t, "You may appeal this ban:", doc.Find("#appeal-form").Prev().Nodes[0].PrevSibling.Data)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "unappealable permaban (banned forever)",
|
||||
|
@ -136,14 +108,22 @@ var (
|
|||
DefaultStyle: "pipes.css",
|
||||
},
|
||||
},
|
||||
expectedOutput: bannedForeverHeader + `You are banned from posting on<span class="ban-boards">all boards</span>for the following reason:` +
|
||||
`<p class="reason">ban message goes here</p>Your ban was placed on<time datetime="0001-01-01T00:00:00Z"class="ban-timestamp">Mon,January 01,0001 12:00:00 AM</time> ` +
|
||||
`and will <span class="ban-timestamp">not expire</span>.<br/>` +
|
||||
`Your IP address is<span class="ban-ip">192.168.56.1</span>.<br /><br/>You may<span class="ban-timestamp">not</span> appeal this ban.<br /></div>` +
|
||||
`<img id="banpage-image" src="/static/permabanned.jpg"/><br/>` +
|
||||
`<audio id="jack"preload="auto"autobuffer loop><source src="/static/hittheroad.ogg"/><source src="/static/hittheroad.wav"/><source src="/static/hittheroad.mp3"/></audio>` +
|
||||
`<script type="text/javascript">document.getElementById("jack").play();</script></div></div>` +
|
||||
footer,
|
||||
validationFunc: func(t *testing.T, reader io.Reader) {
|
||||
doc, err := goquery.NewDocumentFromReader(reader)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, "YOU'RE PERMABANNED,\u00A0IDIOT!", doc.Find("title").Text())
|
||||
assert.Equal(t, "all boards", doc.Find(".ban-boards").Text())
|
||||
assert.Equal(t, "ban message goes here", doc.Find(".reason").Text())
|
||||
banTime := doc.Find(".ban-timestamp").First()
|
||||
assert.Equal(t, "0001-01-01T00:00:00Z", banTime.AttrOr("datetime", ""))
|
||||
assert.Equal(t, "Mon,January 01,0001 12:00:00 AM", banTime.Text())
|
||||
assert.Equal(t, "not expire", banTime.Next().Text())
|
||||
assert.Equal(t, "192.168.56.1", doc.Find(".ban-ip").Text())
|
||||
assert.Equal(t, "/static/permabanned.jpg", doc.Find("img#banpage-image").AttrOr("src", ""))
|
||||
assert.Equal(t, 1, doc.Find("audio#jack").Length())
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "appealable temporary ban",
|
||||
|
@ -166,8 +146,21 @@ var (
|
|||
DefaultStyle: "pipes.css",
|
||||
},
|
||||
},
|
||||
expectedOutput: normalBanHeader +
|
||||
`You are banned from posting on<span class="ban-boards">all boards</span>for the following reason:<p class="reason">ban message goes here</p>Your ban was placed on<time datetime="0001-01-01T00:00:00Z"class="ban-timestamp">Mon,January 01,0001 12:00:00 AM</time> and will expire on <time class="ban-timestamp" datetime="0001-01-01T00:00:00Z">Mon, January 01, 0001 12:00:00 AM</time>.<br/>Your IP address is<span class="ban-ip">192.168.56.1</span>.<br /><br/>You may appeal this ban:<br/><form id="appeal-form"action="/post"method="POST"><input type="hidden"name="board"value=""><input type="hidden"name="banid"value="0"><textarea rows="4"cols="48"name="appealmsg"id="postmsg"placeholder="Appeal message"></textarea><input type="submit"name="doappeal"value="Submit"/><br/></form></div></div></div><footer>Powered by<a href="http://github.com/gochan-org/gochan/">Gochan 4.0</a><br /></footer></div></body></html>`,
|
||||
validationFunc: func(t *testing.T, reader io.Reader) {
|
||||
doc, err := goquery.NewDocumentFromReader(reader)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, "YOU ARE BANNED:(", doc.Find("title").Text())
|
||||
assert.Equal(t, "all boards", doc.Find(".ban-boards").Text())
|
||||
assert.Equal(t, "ban message goes here", doc.Find(".reason").Text())
|
||||
banTime := doc.Find(".ban-timestamp").First()
|
||||
assert.Equal(t, "0001-01-01T00:00:00Z", banTime.AttrOr("datetime", ""))
|
||||
assert.Equal(t, "Mon,January 01,0001 12:00:00 AM", banTime.Text())
|
||||
assert.Equal(t, "Mon, January 01, 0001 12:00:00 AM", banTime.Next().Text())
|
||||
assert.Equal(t, "192.168.56.1", doc.Find(".ban-ip").Text())
|
||||
assert.Equal(t, "You may appeal this ban:", doc.Find("#appeal-form").Prev().Nodes[0].PrevSibling.Data)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "unappealable temporary ban",
|
||||
|
@ -189,12 +182,14 @@ var (
|
|||
DefaultStyle: "pipes.css",
|
||||
},
|
||||
},
|
||||
expectedOutput: normalBanHeader + `You are banned from posting on<span class="ban-boards">all boards</span>for the following reason:` +
|
||||
`<p class="reason">ban message goes here</p>` +
|
||||
`Your ban was placed on<time datetime="0001-01-01T00:00:00Z"class="ban-timestamp">Mon,January 01,0001 12:00:00 AM</time> ` +
|
||||
`and will expire on <time class="ban-timestamp" datetime="0001-01-01T00:00:00Z">Mon, January 01, 0001 12:00:00 AM</time>.<br/>` +
|
||||
`Your IP address is<span class="ban-ip">192.168.56.1</span>.<br /><br/>You may<span class="ban-timestamp">not</span> appeal this ban.<br />` +
|
||||
`</div></div></div>` + footer,
|
||||
validationFunc: func(t *testing.T, reader io.Reader) {
|
||||
doc, err := goquery.NewDocumentFromReader(reader)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, "YOU ARE BANNED:(", doc.Find("title").Text())
|
||||
assert.Equal(t, "You are banned from posting onall boardsfor the following reason:ban message goes hereYour ban was placed onMon,January 01,0001 12:00:00 AM and will expire on Mon, January 01, 0001 12:00:00 AM.Your IP address is192.168.56.1.You maynot appeal this ban.", doc.Find("#ban-info").Text())
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -209,23 +204,105 @@ var (
|
|||
{ID: 1},
|
||||
},
|
||||
},
|
||||
expectedOutput: boardPageHeaderBase +
|
||||
`<form action="/util"method="POST"id="main-form"><div id="right-bottom-content"><div id="report-delbox"><input type="hidden"name="board"value="test"/><input type="hidden"name="boardid"value="1"/><label>[<input type="checkbox"name="fileonly"/>File only]</label> <input type="password" size="10" name="password" id="delete-password" /><input type="submit"name="delete_btn"value="Delete"onclick="return confirm('Are you sure you want to delete these posts?')"/><br/>Report reason:<input type="text"size="10"name="reason"id="reason"/><input type="submit"name="report_btn"value="Report"/><br/><input type="submit"name="edit_btn"value="Edit post"/> <input type="submit"name="move_btn"value="Move thread"/></div></div></form><div id="left-bottom-content"><a href="#"onClick="window.location.reload(); return false;">Update</a>|<a href="#">Scroll to top</a><br/><table id="pages"><tr><td>[<a href="/test/1.html">1</a>]</td></tr></table><span id="boardmenu-bottom">[<a href="/">home</a>] []</span></div>` +
|
||||
footer,
|
||||
getDefaultStyle: true,
|
||||
validationFunc: func(t *testing.T, reader io.Reader) {
|
||||
doc, err := goquery.NewDocumentFromReader(reader)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, "/test/-Testing board", doc.Find("title").Text())
|
||||
assert.Equal(t, "/test/-Testing board", doc.Find("#board-title").Text())
|
||||
assert.Equal(t, "Board for testingCatalog | Bottom", doc.Find("#board-subtitle").Text())
|
||||
assert.Equal(t, 1, doc.Find("#postbox-area").Length())
|
||||
assert.Equal(t, 1, doc.Find("#main-form").Length())
|
||||
assert.Equal(t, 0, doc.Find("#main-form .thread").Length())
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "base case, multi threads and pages",
|
||||
data: map[string]any{
|
||||
"boardConfig": simpleBoardConfig,
|
||||
"board": simpleBoard1,
|
||||
"numPages": 1,
|
||||
"numPages": 2,
|
||||
"sections": []gcsql.Section{
|
||||
{ID: 1},
|
||||
},
|
||||
"threads": []map[string]any{
|
||||
{
|
||||
"Posts": []*building.Post{
|
||||
{
|
||||
ParentID: 1,
|
||||
Post: gcsql.Post{
|
||||
ID: 1,
|
||||
IsTopPost: true,
|
||||
Name: "Test name",
|
||||
Tripcode: "Tripcode",
|
||||
Subject: "Test subject",
|
||||
Message: "Test message",
|
||||
CreatedOn: time.Now(),
|
||||
},
|
||||
},
|
||||
{
|
||||
ParentID: 1,
|
||||
Post: gcsql.Post{
|
||||
ID: 2,
|
||||
Name: "Test name 2",
|
||||
Tripcode: "Tripcode",
|
||||
Message: "Test message 2",
|
||||
CreatedOn: time.Now(),
|
||||
},
|
||||
},
|
||||
},
|
||||
"OmittedPosts": 0,
|
||||
},
|
||||
{
|
||||
"Posts": []*building.Post{
|
||||
{
|
||||
ParentID: 2,
|
||||
Post: gcsql.Post{
|
||||
ID: 3,
|
||||
IsTopPost: true,
|
||||
IsSecureTripcode: true,
|
||||
Name: "Test name 3",
|
||||
Tripcode: "Secure",
|
||||
Subject: "Test subject 3",
|
||||
Message: "Test message 3",
|
||||
CreatedOn: time.Now(),
|
||||
},
|
||||
},
|
||||
},
|
||||
"OmittedPosts": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
getDefaultStyle: true,
|
||||
validationFunc: func(t *testing.T, reader io.Reader) {
|
||||
doc, err := goquery.NewDocumentFromReader(reader)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, "/test/-Testing board", doc.Find("title").Text())
|
||||
assert.Equal(t, "/test/-Testing board", doc.Find("#board-title").Text())
|
||||
assert.Equal(t, "Board for testingCatalog | Bottom", doc.Find("#board-subtitle").Text())
|
||||
assert.Equal(t, 1, doc.Find("#postbox-area").Length())
|
||||
assert.Equal(t, 1, doc.Find("#main-form").Length())
|
||||
|
||||
threads := doc.Find("#main-form .thread")
|
||||
thread1 := doc.Find("#main-form .thread").Eq(0)
|
||||
assert.Equal(t, 2, threads.Length())
|
||||
assert.Equal(t, 1, thread1.Find(".op-post").Length())
|
||||
assert.Equal(t, 1, thread1.Find(".reply-container").Length())
|
||||
assert.Equal(t, "Test name", thread1.Find(".op-post .postername").Text())
|
||||
assert.Equal(t, "!Tripcode", thread1.Find(".op-post .tripcode").Text())
|
||||
|
||||
thread2 := doc.Find("#main-form .thread").Eq(1)
|
||||
assert.Equal(t, 1, thread2.Find(".op-post").Length())
|
||||
assert.Equal(t, 0, thread2.Find(".reply-container").Length())
|
||||
assert.Equal(t, "Test name 3", thread2.Find(".op-post .postername").Text())
|
||||
assert.Equal(t, "!!Secure", thread2.Find(".op-post .tripcode").Text())
|
||||
|
||||
assert.Equal(t, 2, doc.Find("#left-bottom-content #pages a").Length())
|
||||
},
|
||||
expectedOutput: boardPageHeaderBase +
|
||||
`<form action="/util"method="POST"id="main-form"><div id="right-bottom-content"><div id="report-delbox"><input type="hidden"name="board"value="test"/><input type="hidden"name="boardid"value="1"/><label>[<input type="checkbox"name="fileonly"/>File only]</label> <input type="password" size="10" name="password" id="delete-password" /><input type="submit"name="delete_btn"value="Delete"onclick="return confirm('Are you sure you want to delete these posts?')"/><br/>Report reason:<input type="text"size="10"name="reason"id="reason"/><input type="submit"name="report_btn"value="Report"/><br/><input type="submit"name="edit_btn"value="Edit post"/> <input type="submit"name="move_btn"value="Move thread"/></div></div></form><div id="left-bottom-content"><a href="#"onClick="window.location.reload(); return false;">Update</a>|<a href="#">Scroll to top</a><br/><table id="pages"><tr><td>[<a href="/test/1.html">1</a>]</td></tr></table><span id="boardmenu-bottom">[<a href="/">home</a>] []</span></div>` +
|
||||
footer,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -244,7 +321,14 @@ var (
|
|||
".ext": "thumb.png",
|
||||
},
|
||||
},
|
||||
expectedOutput: `const styles=[{Name:"Pipes",Filename:"pipes.css"},{Name:"Yotsuba A",Filename:"yotsuba.css"}];const defaultStyle="pipes.css";const webroot="/";const serverTZ=-1;const fileTypes=[".ext",];`,
|
||||
validationFunc: func(t *testing.T, reader io.Reader) {
|
||||
ba, err := io.ReadAll(reader)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t,
|
||||
`const styles=[{Name:"Pipes",Filename:"pipes.css"},{Name:"Yotsuba A",Filename:"yotsuba.css"}];const defaultStyle="pipes.css";const webroot="/";const serverTZ=-1;const fileTypes=[".ext",];`,
|
||||
string(ba))
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "empty values",
|
||||
|
@ -253,7 +337,14 @@ var (
|
|||
"webroot": "",
|
||||
"timezone": 0,
|
||||
},
|
||||
expectedOutput: `const styles=[];const defaultStyle="";const webroot="";const serverTZ=0;const fileTypes=[];`,
|
||||
validationFunc: func(t *testing.T, reader io.Reader) {
|
||||
ba, err := io.ReadAll(reader)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t,
|
||||
`const styles=[];const defaultStyle="";const webroot="";const serverTZ=0;const fileTypes=[];`,
|
||||
string(ba))
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "escaped string",
|
||||
|
@ -262,7 +353,14 @@ var (
|
|||
"webroot": "",
|
||||
"timezone": 0,
|
||||
},
|
||||
expectedOutput: `const styles=[];const defaultStyle="\"a\\a\"";const webroot="";const serverTZ=0;const fileTypes=[];`,
|
||||
validationFunc: func(t *testing.T, reader io.Reader) {
|
||||
ba, err := io.ReadAll(reader)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t,
|
||||
`const styles=[];const defaultStyle="\"a\\a\"";const webroot="";const serverTZ=0;const fileTypes=[];`,
|
||||
string(ba))
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -277,25 +375,18 @@ var (
|
|||
{ID: 1},
|
||||
},
|
||||
},
|
||||
expectedOutput: footer,
|
||||
},
|
||||
{
|
||||
desc: "base footer test",
|
||||
data: map[string]any{
|
||||
"boardConfig": simpleBoardConfig,
|
||||
"board": simpleBoard2,
|
||||
"numPages": 3,
|
||||
"sections": []gcsql.Section{
|
||||
{ID: 1},
|
||||
},
|
||||
validationFunc: func(t *testing.T, reader io.Reader) {
|
||||
ba, err := io.ReadAll(reader)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, `<footer>Powered by<a href="http://github.com/gochan-org/gochan/">Gochan 4.0</a><br /></footer></div></body></html>`, string(ba))
|
||||
}
|
||||
},
|
||||
expectedOutput: footer,
|
||||
},
|
||||
}
|
||||
|
||||
baseHeaderCases = []templateTestCase{
|
||||
{
|
||||
desc: "Header Test /test/",
|
||||
desc: "Header Test test board",
|
||||
data: map[string]any{
|
||||
"boardConfig": simpleBoardConfig,
|
||||
"board": simpleBoard1,
|
||||
|
@ -304,41 +395,22 @@ var (
|
|||
{ID: 1},
|
||||
},
|
||||
},
|
||||
expectedOutput: headBeginning +
|
||||
`<title>/test/-Testing board</title>` +
|
||||
`<link rel="stylesheet"href="/css/global.css"/>` +
|
||||
`<link id="theme"rel="stylesheet"href="/css/pipes.css"/>` +
|
||||
`<link rel="shortcut icon"href="/favicon.png">` +
|
||||
`<script type="text/javascript"src="/js/consts.js"></script>` +
|
||||
`<script type="text/javascript"src="/js/gochan.js"defer></script>` +
|
||||
`</head><body><div id="topbar"><div class="topbar-section">` +
|
||||
`<a href="/"class="topbar-item">home</a></div>` +
|
||||
`<div class="topbar-section"><a href="/test/"class="topbar-item"title="Testing board">/test/</a>` +
|
||||
`<a href="/test2/" class="topbar-item" title="Testing board#2">/test2/</a></div></div>` +
|
||||
`<div id="content">`,
|
||||
},
|
||||
{
|
||||
desc: "Header Test /sup/",
|
||||
data: map[string]any{
|
||||
"boardConfig": simpleBoardConfig,
|
||||
"board": simpleBoard2,
|
||||
"numPages": 1,
|
||||
"sections": []gcsql.Section{
|
||||
{ID: 1},
|
||||
},
|
||||
getDefaultStyle: true,
|
||||
validationFunc: func(t *testing.T, reader io.Reader) {
|
||||
doc, err := goquery.NewDocumentFromReader(reader)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, "/test/-Testing board", doc.Find("title").Text())
|
||||
assert.Equal(t, "/css/global.css", doc.Find("link[rel='stylesheet']").AttrOr("href", ""))
|
||||
assert.Equal(t, "/css/pipes.css", doc.Find("link#theme").AttrOr("href", ""))
|
||||
assert.Equal(t, "/favicon.png", doc.Find("link[rel='shortcut icon']").AttrOr("href", ""))
|
||||
assert.Equal(t, "/js/consts.js", doc.Find("script[src='/js/consts.js']").AttrOr("src", ""))
|
||||
assert.Equal(t, "/js/gochan.js", doc.Find("script[src='/js/gochan.js']").AttrOr("src", ""))
|
||||
assert.Equal(t, "home", doc.Find("#topbar a.topbar-item").First().Text())
|
||||
assert.Equal(t, "/test/", doc.Find("#topbar a.topbar-item").Eq(1).Text())
|
||||
assert.Equal(t, "/test2/", doc.Find("#topbar a.topbar-item").Eq(2).Text())
|
||||
},
|
||||
expectedOutput: headBeginning +
|
||||
`<title>/sup/-Gochan Support board</title>` +
|
||||
`<link rel="stylesheet"href="/css/global.css"/>` +
|
||||
`<link id="theme"rel="stylesheet"href="/css/pipes.css"/>` +
|
||||
`<link rel="shortcut icon"href="/favicon.png">` +
|
||||
`<script type="text/javascript"src="/js/consts.js"></script>` +
|
||||
`<script type="text/javascript"src="/js/gochan.js"defer></script>` +
|
||||
`</head><body><div id="topbar"><div class="topbar-section">` +
|
||||
`<a href="/"class="topbar-item">home</a></div>` +
|
||||
`<div class="topbar-section"><a href="/test/"class="topbar-item"title="Testing board">/test/</a>` +
|
||||
`<a href="/test2/" class="topbar-item" title="Testing board#2">/test2/</a></div></div>` +
|
||||
`<div id="content">`,
|
||||
},
|
||||
{
|
||||
desc: "Perma Ban Header Test",
|
||||
|
@ -362,141 +434,43 @@ var (
|
|||
DefaultStyle: "pipes.css",
|
||||
},
|
||||
},
|
||||
expectedOutput: `<!DOCTYPE html><html lang="en"><head>` +
|
||||
`<meta charset="UTF-8"><meta name="viewport"content="width=device-width, initial-scale=1.0">` +
|
||||
`<title>YOU'RE PERMABANNED, IDIOT!</title><link rel="stylesheet"href="/css/global.css"/>` +
|
||||
`<link id="theme"rel="stylesheet"href="/css/pipes.css"/><link rel="shortcut icon"href="/favicon.png">` +
|
||||
`<script type="text/javascript"src="/js/consts.js"></script><script type="text/javascript"src="/js/gochan.js"defer></script>` +
|
||||
`</head><body><div id="topbar"><div class="topbar-section"><a href="/"class="topbar-item">home</a></div></div><div id="content">`,
|
||||
},
|
||||
{
|
||||
desc: "Appealable Perma Ban Header Test",
|
||||
data: map[string]any{
|
||||
"ban": &gcsql.IPBan{
|
||||
RangeStart: "192.168.56.0",
|
||||
RangeEnd: "192.168.56.255",
|
||||
IPBanBase: gcsql.IPBanBase{
|
||||
Permanent: true,
|
||||
CanAppeal: true,
|
||||
StaffID: 1,
|
||||
Message: "ban message goes here",
|
||||
},
|
||||
},
|
||||
"ip": "192.168.56.1",
|
||||
"siteConfig": testingSiteConfig,
|
||||
"systemCritical": config.SystemCriticalConfig{
|
||||
WebRoot: "/",
|
||||
},
|
||||
"boardConfig": config.BoardConfig{
|
||||
DefaultStyle: "pipes.css",
|
||||
},
|
||||
validationFunc: func(t *testing.T, reader io.Reader) {
|
||||
doc, err := goquery.NewDocumentFromReader(reader)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, "YOU'RE PERMABANNED,\u00a0IDIOT!", doc.Find("title").Text())
|
||||
assert.Equal(t, "/css/global.css", doc.Find("link[rel='stylesheet']").AttrOr("href", ""))
|
||||
assert.Equal(t, "/css/pipes.css", doc.Find("link#theme").AttrOr("href", ""))
|
||||
assert.Equal(t, "/favicon.png", doc.Find("link[rel='shortcut icon']").AttrOr("href", ""))
|
||||
assert.Equal(t, "/js/consts.js", doc.Find("script[src='/js/consts.js']").AttrOr("src", ""))
|
||||
assert.Equal(t, "/js/gochan.js", doc.Find("script[src='/js/gochan.js']").AttrOr("src", ""))
|
||||
},
|
||||
expectedOutput: `<!DOCTYPE html><html lang="en"><head>` +
|
||||
`<meta charset="UTF-8"><meta name="viewport"content="width=device-width, initial-scale=1.0">` +
|
||||
`<title>YOU ARE BANNED:(</title><link rel="stylesheet"href="/css/global.css"/>` +
|
||||
`<link id="theme"rel="stylesheet"href="/css/pipes.css"/><link rel="shortcut icon"href="/favicon.png">` +
|
||||
`<script type="text/javascript"src="/js/consts.js"></script><script type="text/javascript"src="/js/gochan.js"defer></script>` +
|
||||
`</head><body><div id="topbar"><div class="topbar-section"><a href="/"class="topbar-item">home</a></div></div><div id="content">`,
|
||||
},
|
||||
{
|
||||
desc: "Appealable Temp Ban Header Test",
|
||||
data: map[string]any{
|
||||
"ban": &gcsql.IPBan{
|
||||
RangeStart: "192.168.56.0",
|
||||
RangeEnd: "192.168.56.255",
|
||||
IPBanBase: gcsql.IPBanBase{
|
||||
CanAppeal: true,
|
||||
StaffID: 1,
|
||||
Message: "ban message goes here",
|
||||
},
|
||||
},
|
||||
"ip": "192.168.56.1",
|
||||
"siteConfig": testingSiteConfig,
|
||||
"systemCritical": config.SystemCriticalConfig{
|
||||
WebRoot: "/",
|
||||
},
|
||||
"boardConfig": config.BoardConfig{
|
||||
DefaultStyle: "pipes.css",
|
||||
},
|
||||
},
|
||||
expectedOutput: `<!DOCTYPE html><html lang="en">` +
|
||||
`<head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, initial-scale=1.0">` +
|
||||
`<title>YOU ARE BANNED:(</title><link rel="stylesheet"href="/css/global.css"/>` +
|
||||
`<link id="theme"rel="stylesheet"href="/css/pipes.css"/><link rel="shortcut icon"href="/favicon.png">` +
|
||||
`<script type="text/javascript"src="/js/consts.js"></script><script type="text/javascript"src="/js/gochan.js"defer></script>` +
|
||||
`</head><body><div id="topbar"><div class="topbar-section"><a href="/"class="topbar-item">home</a></div></div><div id="content">`,
|
||||
},
|
||||
{
|
||||
desc: "Unappealable Temp Ban Header Test",
|
||||
data: map[string]any{
|
||||
"ban": &gcsql.IPBan{
|
||||
RangeStart: "192.168.56.0",
|
||||
RangeEnd: "192.168.56.255",
|
||||
IPBanBase: gcsql.IPBanBase{
|
||||
StaffID: 1,
|
||||
Message: "ban message goes here",
|
||||
},
|
||||
},
|
||||
"ip": "192.168.56.1",
|
||||
"siteConfig": testingSiteConfig,
|
||||
"systemCritical": config.SystemCriticalConfig{
|
||||
WebRoot: "/",
|
||||
},
|
||||
"boardConfig": config.BoardConfig{
|
||||
DefaultStyle: "pipes.css",
|
||||
},
|
||||
},
|
||||
expectedOutput: `<!DOCTYPE html><html lang="en"><head>` +
|
||||
`<meta charset="UTF-8"><meta name="viewport"content="width=device-width, initial-scale=1.0">` +
|
||||
`<title>YOU ARE BANNED:(</title><link rel="stylesheet"href="/css/global.css"/>` +
|
||||
`<link id="theme"rel="stylesheet"href="/css/pipes.css"/><link rel="shortcut icon"href="/favicon.png">` +
|
||||
`<script type="text/javascript"src="/js/consts.js"></script><script type="text/javascript"src="/js/gochan.js"defer></script>` +
|
||||
`</head><body><div id="topbar"><div class="topbar-section"><a href="/"class="topbar-item">home</a></div></div><div id="content">`,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
boardPageHeaderBase = `<!DOCTYPE html><html lang="en"><head>` +
|
||||
`<meta charset="UTF-8"><meta name="viewport"content="width=device-width, initial-scale=1.0">` +
|
||||
`<title>/test/-Testing board</title>` +
|
||||
`<link rel="stylesheet"href="/css/global.css"/><link id="theme"rel="stylesheet"href="/css/pipes.css"/>` +
|
||||
`<link rel="shortcut icon"href="/favicon.png">` +
|
||||
`<script type="text/javascript"src="/js/consts.js"></script><script type="text/javascript"src="/js/gochan.js"defer></script></head>` +
|
||||
`<body><div id="topbar"><div class="topbar-section"><a href="/"class="topbar-item">home</a></div>` +
|
||||
`<div class="topbar-section"><a href="/test/"class="topbar-item"title="Testing board">/test/</a><a href="/test2/" class="topbar-item" title="Testing board#2">/test2/</a></div></div>` +
|
||||
`<div id="content"><header><h1 id="board-title">/test/-Testing board</h1><div id="board-subtitle">Board for testing<br/><a href="/test/catalog.html">Catalog</a> | <a href="#footer">Bottom</a></div></header><hr />` +
|
||||
`<div id="postbox-area"><form id="postform"name="postform"action="/post"method="POST"enctype="multipart/form-data">` +
|
||||
`<input type="hidden"name="threadid"value="0"/><input type="hidden"name="boardid"value="1"/>` +
|
||||
`<table id="postbox-static"><tr><th class="postblock">Name</th><td><input type="text" name="postname" maxlength="100" size="25" /></td></tr>` +
|
||||
`<tr><th class="postblock">Email</th><td><input type="text" name="postemail" maxlength="100" size="25" /></td></tr>` +
|
||||
`<tr><th class="postblock">Subject</th><td><input type="text"name="postsubject"size="25"maxlength="100"><input type="text"name="username"style="display:none"/><input type="submit"value="Post"/></td></tr>` +
|
||||
`<tr><th class="postblock">Message</th><td><textarea rows="5" cols="35" name="postmsg" id="postmsg"></textarea></td></tr>` +
|
||||
`<tr><th class="postblock">File</th><td><input name="imagefile" type="file" accept="image/jpeg,image/png,image/gif,video/webm,video/mp4">` +
|
||||
`<label for="spoiler"><input type="checkbox" id="spoiler" name="spoiler"/>Spoiler</label></td></tr>` +
|
||||
`<tr id="threadoptions"style="display: none;"><th class="postblock">Options</th><td></td></tr>` +
|
||||
`<tr><th class="postblock">Password</th><td><input type="password" id="postpassword" name="postpassword" size="14" />(for post/file deletion)</td></tr></table>` +
|
||||
`<input type="password" name="dummy2" style="display:none"/></form></div><hr />`
|
||||
)
|
||||
|
||||
type templateTestCase struct {
|
||||
desc string
|
||||
data any
|
||||
expectsError bool
|
||||
expectedOutput string
|
||||
desc string
|
||||
data any
|
||||
expectsError bool
|
||||
getDefaultStyle bool
|
||||
|
||||
validationFunc func(t *testing.T, reader io.Reader)
|
||||
}
|
||||
|
||||
func (tC *templateTestCase) Run(t *testing.T, templateName string) {
|
||||
buf := new(bytes.Buffer)
|
||||
err := serverutil.MinifyTemplate(templateName, tC.data, buf, "text/javascript")
|
||||
var buf bytes.Buffer
|
||||
|
||||
err := serverutil.MinifyTemplate(templateName, tC.data, &buf, "text/javascript")
|
||||
if tC.expectsError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
if !assert.NoError(t, err) {
|
||||
// var allStaff []gcsql.Staff
|
||||
|
||||
return
|
||||
t.FailNow()
|
||||
}
|
||||
if assert.NotNilf(t, tC.validationFunc, "Validation function for %q is not implemented", tC.desc) {
|
||||
tC.validationFunc(t, &buf)
|
||||
}
|
||||
assert.Equal(t, tC.expectedOutput, buf.String())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,11 +28,10 @@ const (
|
|||
`ORDER BY\s+position ASC,\s*name ASC`
|
||||
)
|
||||
|
||||
func initTemplatesMock(t *testing.T, mock sqlmock.Sqlmock, which ...string) bool {
|
||||
t.Helper()
|
||||
func initTemplatesMock(t *testing.T, mock sqlmock.Sqlmock, which ...string) {
|
||||
_, err := testutil.GoToGochanRoot(t)
|
||||
if !assert.NoError(t, err) {
|
||||
return false
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
rows := sqlmock.NewRows([]string{"boards.id", "section_id", "uri", "dir", "navbar_position", "title",
|
||||
|
@ -55,14 +54,17 @@ func initTemplatesMock(t *testing.T, mock sqlmock.Sqlmock, which ...string) bool
|
|||
|
||||
config.SetTestTemplateDir("templates")
|
||||
|
||||
if !assert.NoError(t, gctemplates.InitTemplates(which...)) {
|
||||
return false
|
||||
if !assert.NoError(t, gctemplates.InitTemplates()) {
|
||||
t.FailNow()
|
||||
}
|
||||
return assert.NoError(t, mock.ExpectationsWereMet())
|
||||
|
||||
if !assert.NoError(t, mock.ExpectationsWereMet()) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func runTemplateTestCases(t *testing.T, templateName string, testCases []templateTestCase) {
|
||||
t.Helper()
|
||||
db, mock, err := sqlmock.New()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
|
@ -73,12 +75,14 @@ func runTemplateTestCases(t *testing.T, templateName string, testCases []templat
|
|||
return
|
||||
}
|
||||
|
||||
if !initTemplatesMock(t, mock) {
|
||||
return
|
||||
}
|
||||
initTemplatesMock(t, mock, templateName)
|
||||
|
||||
serverutil.InitMinifier()
|
||||
for _, tC := range testCases {
|
||||
if tC.getDefaultStyle {
|
||||
mock.ExpectPrepare(`SELECT default_style FROM boards WHERE dir = \?`).ExpectQuery().
|
||||
WithArgs("test").WillReturnRows(sqlmock.NewRows([]string{"default_style"}).AddRow("pipes.css"))
|
||||
}
|
||||
t.Run(tC.desc, func(t *testing.T) {
|
||||
tC.Run(t, templateName)
|
||||
})
|
||||
|
@ -98,16 +102,7 @@ func TestJsConstsTemplate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTemplateBase(t *testing.T) {
|
||||
db, mock, err := sqlmock.New()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
config.SetTestDBConfig("mysql", "localhost", "gochan", "gochan", "gochan", "")
|
||||
if !assert.NoError(t, gcsql.SetTestingDB("mysql", "gochan", "", db)) {
|
||||
return
|
||||
}
|
||||
|
||||
initTemplatesMock(t, mock)
|
||||
runTemplateTestCases(t, "", nil)
|
||||
}
|
||||
|
||||
func TestBaseFooter(t *testing.T) {
|
||||
|
|
|
@ -423,6 +423,13 @@ func MakePost(writer http.ResponseWriter, request *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
isSpoileredThread := request.PostFormValue("spoilerthread") == "on"
|
||||
if isSpoileredThread && !boardConfig.EnableSpoileredThreads {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
server.ServeError(writer, "Board does not support spoilered threads", wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var delay int
|
||||
var tooSoon bool
|
||||
if post.ThreadID == 0 {
|
||||
|
@ -433,6 +440,10 @@ func MakePost(writer http.ResponseWriter, request *http.Request) {
|
|||
// replying to a thread
|
||||
delay, err = gcsql.SinceLastPost(post.IP)
|
||||
tooSoon = delay < boardConfig.Cooldowns.Reply
|
||||
if isSpoileredThread {
|
||||
warnEv.Msg("User submitted a form with spoilered thread enabled while replying to a thread")
|
||||
server.ServeError(writer, server.NewServerError("Invalid request", http.StatusBadRequest), wantsJSON, nil)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
errEv.Err(err).Caller().Str("boardDir", board.Dir).Msg("Unable to check post cooldown")
|
||||
|
@ -526,7 +537,15 @@ func MakePost(writer http.ResponseWriter, request *http.Request) {
|
|||
return
|
||||
}
|
||||
_, emailCommand := getEmailAndCommand(request)
|
||||
if err = post.Insert(emailCommand != "sage", board.ID, isLocked, isSticky, false, isCyclic); err != nil {
|
||||
|
||||
thread := &gcsql.Thread{
|
||||
BoardID: board.ID,
|
||||
Locked: isLocked,
|
||||
Stickied: isSticky,
|
||||
Anchored: emailCommand == "sage" && post.ThreadID == 0,
|
||||
}
|
||||
|
||||
if err = post.Insert(emailCommand != "sage", thread, false); err != nil {
|
||||
errEv.Err(err).Caller().
|
||||
Str("sql", "postInsertion").
|
||||
Msg("Unable to insert post")
|
||||
|
|
|
@ -36,7 +36,7 @@ coalesce(f.thumbnail_width, 0) AS tw,
|
|||
coalesce(f.thumbnail_height, 0) AS th,
|
||||
coalesce(f.width, 0) AS width,
|
||||
coalesce(f.height, 0) AS height,
|
||||
t.locked, t.stickied, t.cyclical, flag, country, p.is_deleted
|
||||
t.locked, t.stickied, t.cyclical, t.spoilered, flag, country, p.is_deleted
|
||||
FROM DBPREFIXposts p
|
||||
LEFT JOIN DBPREFIXfiles f ON f.post_id = p.id AND p.is_deleted = FALSE
|
||||
LEFT JOIN DBPREFIXthreads t ON t.id = p.thread_id
|
||||
|
|
|
@ -41,9 +41,12 @@
|
|||
<tr id="threadoptions" {{if $noCyclicThreads}}style="display: none;"{{end}}>
|
||||
<th class="postblock">Options</th>
|
||||
<td>
|
||||
{{if not $noCyclicThreads}}
|
||||
<label for="cyclic"><input type="checkbox" name="cyclic" id="cyclic"> Cyclic thread</label>
|
||||
{{end}}
|
||||
{{- if not $noCyclicThreads -}}
|
||||
<label for="cyclic"><input type="checkbox" name="cyclic" id="cyclic"> Cyclic thread</label>
|
||||
{{- end -}}
|
||||
{{- if $.boardConfig.EnableSpoileredThreads -}}
|
||||
<label for="spoilerthread"><input type="checkbox" name="spoilerthread" id="spoilerthread"> Spoiler thread</label>
|
||||
{{- end -}}
|
||||
</td>
|
||||
</tr>{{end}}
|
||||
<tr><th class="postblock">Password</th><td><input type="password" id="postpassword" name="postpassword" size="14" /> (for post/file deletion)</td></tr>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue