/* 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 . */ 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", }, }