about summary refs log blame commit diff stats
path: root/registry/fetch_test.go
blob: 2a7c471d3f52288786088ba68596eb983d597325 (plain) (tree)





































































































                                                                                   
                                                                                











































































































































































                                                                                                           
/*
Copyright (c) 2019 Ben Morrison (gbmor)

This file is part of Registry.

Registry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Registry 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with Registry.  If not, see <https://www.gnu.org/licenses/>.
*/

package registry

import (
	"bufio"
	"fmt"
	"net/http"
	"os"
	"strings"
	"testing"
	"time"
)

func constructTwtxt() []byte {
	registry := initTestEnv()
	var resp []byte
	// iterates through each mock user's mock statuses
	for _, v := range registry.Users {
		for _, e := range v.Status {
			split := strings.Split(e, "\t")
			status := []byte(split[2] + "\t" + split[3] + "\n")
			resp = append(resp, status...)
		}
	}
	return resp
}

// this is just dumping all the mock statuses.
// it'll be served under fake paths as
// "remote" twtxt.txt files
func twtxtHandler(w http.ResponseWriter, _ *http.Request) {
	// prepare the response
	resp := constructTwtxt()
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	n, err := w.Write(resp)
	if err != nil || n == 0 {
		fmt.Printf("Got error or wrote zero bytes: %v bytes, %v\n", n, err)
	}
}

var getTwtxtCases = []struct {
	name      string
	url       string
	wantErr   bool
	localOnly bool
}{
	{
		name:      "Constructed Local twtxt.txt",
		url:       "http://localhost:8080/twtxt.txt",
		wantErr:   false,
		localOnly: true,
	},
	{
		name:      "Inaccessible Site With twtxt.txt",
		url:       "https://example33333333333.com/twtxt.txt",
		wantErr:   true,
		localOnly: false,
	},
	{
		name:      "Inaccessible Site Without twtxt.txt",
		url:       "https://example333333333333.com",
		wantErr:   true,
		localOnly: false,
	},
	{
		name:      "Local File Inclusion 1",
		url:       "file://init_test.go",
		wantErr:   true,
		localOnly: false,
	},
	{
		name:      "Local File Inclusion 2",
		url:       "/etc/passwd",
		wantErr:   true,
		localOnly: false,
	},
	{
		name:      "Remote File Inclusion",
		url:       "https://example.com/file.cgi",
		wantErr:   true,
		localOnly: false,
	},
	{
		name:      "Remote Registry",
		url:       "https://twtxt.tilde.institute/api/plain/tweets/all",
		wantErr:   false,
		localOnly: false,
	},
	{
		name:      "Garbage Data",
		url:       "this will be replaced with garbage data",
		wantErr:   true,
		localOnly: true,
	},
}

// Test the function that yoinks the /twtxt.txt file
// for a given user.
func Test_GetTwtxt(t *testing.T) {
	var buf = make([]byte, 256)
	// read random data into case 4
	rando, _ := os.Open("/dev/random")
	reader := bufio.NewReader(rando)
	n, err := reader.Read(buf)
	if err != nil || n == 0 {
		t.Errorf("Couldn't set up test: %v\n", err)
	}
	getTwtxtCases[7].url = string(buf)

	if !getTwtxtCases[0].localOnly {
		http.Handle("/twtxt.txt", http.HandlerFunc(twtxtHandler))
		go fmt.Println(http.ListenAndServe(":8080", nil))
	}

	for _, tt := range getTwtxtCases {
		t.Run(tt.name, func(t *testing.T) {
			if tt.localOnly {
				t.Skipf("Local-only test. Skipping ... \n")
			}
			out, _, err := GetTwtxt(tt.url, nil)
			if tt.wantErr && err == nil {
				t.Errorf("Expected error: %v\n", tt.url)
			}
			if !tt.wantErr && err != nil {
				t.Errorf("Unexpected error: %v %v\n", tt.url, err)
			}
			if !tt.wantErr && out == nil {
				t.Errorf("Incorrect data received: %v\n", out)
			}
		})
	}

}

// running the benchmarks separately for each case
// as they have different properties (allocs, time)
func Benchmark_GetTwtxt(b *testing.B) {

	for i := 0; i < b.N; i++ {
		_, _, err := GetTwtxt("https://gbmor.dev/twtxt.txt", nil)
		if err != nil {
			continue
		}
	}
}

var parseTwtxtCases = []struct {
	name      string
	data      []byte
	wantErr   bool
	localOnly bool
}{
	{
		name:      "Constructed twtxt file",
		data:      constructTwtxt(),
		wantErr:   false,
		localOnly: false,
	},
	{
		name:      "Incorrectly formatted date",
		data:      []byte("2019 April 23rd\tI love twtxt!!!11"),
		wantErr:   true,
		localOnly: false,
	},
	{
		name:      "No data",
		data:      []byte{},
		wantErr:   true,
		localOnly: false,
	},
	{
		name:      "Variant rfc3339 datestamp",
		data:      []byte("2020-02-04T21:28:21.868659+00:00\tWill this work?"),
		wantErr:   false,
		localOnly: false,
	},
	{
		name:      "Random/garbage data",
		wantErr:   true,
		localOnly: true,
	},
}

// See if we can break ParseTwtxt or get it
// to throw an unexpected error
func Test_ParseUserTwtxt(t *testing.T) {
	var buf = make([]byte, 256)
	// read random data into case 4
	rando, _ := os.Open("/dev/random")
	reader := bufio.NewReader(rando)
	n, err := reader.Read(buf)
	if err != nil || n == 0 {
		t.Errorf("Couldn't set up test: %v\n", err)
	}
	parseTwtxtCases[4].data = buf

	for _, tt := range parseTwtxtCases {
		if tt.localOnly {
			t.Skipf("Local-only test: Skipping ... \n")
		}
		t.Run(tt.name, func(t *testing.T) {
			timemap, errs := ParseUserTwtxt(tt.data, "testuser", "testurl")
			if errs == nil && tt.wantErr {
				t.Errorf("Expected error(s), received none.\n")
			}

			if !tt.wantErr {
				if errs != nil {
					t.Errorf("Unexpected error: %v\n", errs)
				}

				for k, v := range timemap {
					if k == (time.Time{}) || v == "" {
						t.Errorf("Empty status or empty timestamp: %v, %v\n", k, v)
					}
				}
			}
		})
	}
}

func Benchmark_ParseUserTwtxt(b *testing.B) {
	var buf = make([]byte, 256)
	// read random data into case 4
	rando, _ := os.Open("/dev/random")
	reader := bufio.NewReader(rando)
	n, err := reader.Read(buf)
	if err != nil || n == 0 {
		b.Errorf("Couldn't set up benchmark: %v\n", err)
	}
	parseTwtxtCases[3].data = buf

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		for _, tt := range parseTwtxtCases {
			_, _ = ParseUserTwtxt(tt.data, "testuser", "testurl")
		}
	}
}

var timestampCases = []struct {
	name     string
	orig     string
	expected string
}{
	{
		name:     "Timezone appended",
		orig:     "2020-01-13T16:08:25.544735+00:00",
		expected: "2020-01-13T16:08:25.544735Z",
	},
	{
		name:     "It's fine already",
		orig:     "2020-01-14T00:19:45.092344Z",
		expected: "2020-01-14T00:19:45.092344Z",
	},
}