mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-03 22:32:25 -06:00 
			
		
		
		
	* improved server shutdown with more precise shutdown of modules + deferring of ALL of it
* move delivery and workers into separate files
* add worker task model and Serialize() / Deserialize() methods for message types
* start adding message serialize / deserialize tests
* start adding test cases
* update body rewinding to rely on standard library mechanism of r.GetBody()
* remove request rewinding (http.Client{} should already handle this)
* standard library already handles rewinding
* improved code comment
* move the newPOST() function contents to prepare(), fits better with current API
* add Serialize() / Deserialize() implementations for Delivery{} type
* finish writing FromClientAPI sserialize / deserialize tests
* start adding FromFediAPI{} serialize / deserialize test cases
* fix FromFediAPI{} tests
* add tests for delivery message type
* fix repeat code
* missing license header
* use testrig status and accounts for marshal / unmarshaling tests
* add a specific test for checking account RSA keys are preserved
		
	
			
		
			
				
	
	
		
			220 lines
		
	
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			220 lines
		
	
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// GoToSocial
 | 
						|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
 | 
						|
// SPDX-License-Identifier: AGPL-3.0-or-later
 | 
						|
//
 | 
						|
// This program is free software: you can redistribute it and/or modify
 | 
						|
// it under the terms of the GNU Affero General Public License as published by
 | 
						|
// the Free Software Foundation, either version 3 of the License, or
 | 
						|
// (at your option) any later version.
 | 
						|
//
 | 
						|
// This program is distributed in the hope that it will be useful,
 | 
						|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
// GNU Affero General Public License for more details.
 | 
						|
//
 | 
						|
// You should have received a copy of the GNU Affero General Public License
 | 
						|
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
package delivery_test
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"math/rand"
 | 
						|
	"net"
 | 
						|
	"net/http"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	"codeberg.org/gruf/go-byteutil"
 | 
						|
	"github.com/superseriousbusiness/gotosocial/internal/config"
 | 
						|
	"github.com/superseriousbusiness/gotosocial/internal/httpclient"
 | 
						|
	"github.com/superseriousbusiness/gotosocial/internal/queue"
 | 
						|
	"github.com/superseriousbusiness/gotosocial/internal/transport/delivery"
 | 
						|
)
 | 
						|
 | 
						|
func TestDeliveryWorkerPool(t *testing.T) {
 | 
						|
	for _, i := range []int{1, 2, 4, 8, 16, 32} {
 | 
						|
		t.Run("size="+strconv.Itoa(i), func(t *testing.T) {
 | 
						|
			testDeliveryWorkerPool(t, i, generateInput(100*i))
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func testDeliveryWorkerPool(t *testing.T, sz int, input []*testrequest) {
 | 
						|
	wp := new(delivery.WorkerPool)
 | 
						|
	wp.Init(httpclient.New(httpclient.Config{
 | 
						|
		AllowRanges: config.MustParseIPPrefixes([]string{
 | 
						|
			"127.0.0.0/8",
 | 
						|
		}),
 | 
						|
	}))
 | 
						|
	wp.Start(sz)
 | 
						|
	defer wp.Stop()
 | 
						|
	test(t, &wp.Queue, input)
 | 
						|
}
 | 
						|
 | 
						|
func test(
 | 
						|
	t *testing.T,
 | 
						|
	queue *queue.StructQueue[*delivery.Delivery],
 | 
						|
	input []*testrequest,
 | 
						|
) {
 | 
						|
	expect := make(chan *testrequest)
 | 
						|
	errors := make(chan error)
 | 
						|
 | 
						|
	// Prepare an HTTP test handler that ensures expected delivery is received.
 | 
						|
	handler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
 | 
						|
		errors <- (<-expect).Equal(r)
 | 
						|
	})
 | 
						|
 | 
						|
	// Start new HTTP test server listener.
 | 
						|
	l, err := net.Listen("tcp", "127.0.0.1:0")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	defer l.Close()
 | 
						|
 | 
						|
	// Start the HTTP server.
 | 
						|
	//
 | 
						|
	// specifically not using httptest.Server{} here as httptest
 | 
						|
	// links that server with its own http.Client{}, whereas we're
 | 
						|
	// using an httpclient.Client{} (well, delivery routine is).
 | 
						|
	srv := new(http.Server)
 | 
						|
	srv.Addr = "http://" + l.Addr().String()
 | 
						|
	srv.Handler = handler
 | 
						|
	go srv.Serve(l)
 | 
						|
	defer srv.Close()
 | 
						|
 | 
						|
	// Range over test input.
 | 
						|
	for _, test := range input {
 | 
						|
 | 
						|
		// Generate req for input.
 | 
						|
		req := test.Generate(srv.Addr)
 | 
						|
		r := httpclient.WrapRequest(req)
 | 
						|
 | 
						|
		// Wrap the request in delivery.
 | 
						|
		dlv := new(delivery.Delivery)
 | 
						|
		dlv.Request = r
 | 
						|
 | 
						|
		// Enqueue delivery!
 | 
						|
		queue.Push(dlv)
 | 
						|
		expect <- test
 | 
						|
 | 
						|
		// Wait for errors from handler.
 | 
						|
		if err := <-errors; err != nil {
 | 
						|
			t.Error(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type testrequest struct {
 | 
						|
	method string
 | 
						|
	uri    string
 | 
						|
	body   []byte
 | 
						|
}
 | 
						|
 | 
						|
// generateInput generates 'n' many testrequest cases.
 | 
						|
func generateInput(n int) []*testrequest {
 | 
						|
	tests := make([]*testrequest, n)
 | 
						|
	for i := range tests {
 | 
						|
		tests[i] = new(testrequest)
 | 
						|
		tests[i].method = randomMethod()
 | 
						|
		tests[i].uri = randomURI()
 | 
						|
		tests[i].body = randomBody(tests[i].method)
 | 
						|
	}
 | 
						|
	return tests
 | 
						|
}
 | 
						|
 | 
						|
var methods = []string{
 | 
						|
	http.MethodConnect,
 | 
						|
	http.MethodDelete,
 | 
						|
	http.MethodGet,
 | 
						|
	http.MethodHead,
 | 
						|
	http.MethodOptions,
 | 
						|
	http.MethodPatch,
 | 
						|
	http.MethodPost,
 | 
						|
	http.MethodPut,
 | 
						|
	http.MethodTrace,
 | 
						|
}
 | 
						|
 | 
						|
// randomMethod generates a random http method.
 | 
						|
func randomMethod() string {
 | 
						|
	return methods[rand.Intn(len(methods))]
 | 
						|
}
 | 
						|
 | 
						|
// randomURI generates a random http uri.
 | 
						|
func randomURI() string {
 | 
						|
	n := rand.Intn(5)
 | 
						|
	p := make([]string, n)
 | 
						|
	for i := range p {
 | 
						|
		p[i] = strconv.Itoa(rand.Int())
 | 
						|
	}
 | 
						|
	return "/" + strings.Join(p, "/")
 | 
						|
}
 | 
						|
 | 
						|
// randomBody generates a random http body DEPENDING on method.
 | 
						|
func randomBody(method string) []byte {
 | 
						|
	if requiresBody(method) {
 | 
						|
		return []byte(method + " " + randomURI())
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// requiresBody returns whether method requires body.
 | 
						|
func requiresBody(method string) bool {
 | 
						|
	switch method {
 | 
						|
	case http.MethodPatch,
 | 
						|
		http.MethodPost,
 | 
						|
		http.MethodPut:
 | 
						|
		return true
 | 
						|
	default:
 | 
						|
		return false
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Generate will generate a real http.Request{} from test data.
 | 
						|
func (t *testrequest) Generate(addr string) *http.Request {
 | 
						|
	var body io.ReadCloser
 | 
						|
	if t.body != nil {
 | 
						|
		var b byteutil.ReadNopCloser
 | 
						|
		b.Reset(t.body)
 | 
						|
		body = &b
 | 
						|
	}
 | 
						|
	req, err := http.NewRequest(t.method, addr+t.uri, body)
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
	return req
 | 
						|
}
 | 
						|
 | 
						|
// Equal checks if request matches receiving test request.
 | 
						|
func (t *testrequest) Equal(r *http.Request) error {
 | 
						|
	// Ensure methods match.
 | 
						|
	if t.method != r.Method {
 | 
						|
		return fmt.Errorf("differing request methods: t=%q r=%q", t.method, r.Method)
 | 
						|
	}
 | 
						|
 | 
						|
	// Ensure request URIs match.
 | 
						|
	if t.uri != r.URL.RequestURI() {
 | 
						|
		return fmt.Errorf("differing request urls: t=%q r=%q", t.uri, r.URL.RequestURI())
 | 
						|
	}
 | 
						|
 | 
						|
	// Ensure body cases match.
 | 
						|
	if requiresBody(t.method) {
 | 
						|
 | 
						|
		// Read request into memory.
 | 
						|
		b, err := io.ReadAll(r.Body)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("error reading request body: %v", err)
 | 
						|
		}
 | 
						|
 | 
						|
		// Compare the request bodies.
 | 
						|
		st := strings.TrimSpace(string(t.body))
 | 
						|
		sr := strings.TrimSpace(string(b))
 | 
						|
		if st != sr {
 | 
						|
			return fmt.Errorf("differing request bodies: t=%q r=%q", st, sr)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 |