mirror of
https://codeberg.org/scip/ephemerup.git
synced 2025-12-17 04:30:57 +01:00
changes:
- added unit tests
- put all subcmds into one file
- use io.Writer for output, better for testing
- added upload form support
- added api docs
- generalized db engine
- added mail notify support for forms
- enhanced server/SetupAuthStore() to also look up form ids
- added form template (put into .go file by Makefile
- renamed project
This commit is contained in:
@@ -25,8 +25,9 @@ import (
|
||||
"github.com/imroc/req/v3"
|
||||
"github.com/jarcoal/httpmock"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
"github.com/tlinden/cenophane/common"
|
||||
"github.com/tlinden/cenophane/upctl/cfg"
|
||||
"github.com/tlinden/ephemerup/common"
|
||||
"github.com/tlinden/ephemerup/upctl/cfg"
|
||||
"io"
|
||||
"mime"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -49,8 +50,11 @@ type ListParams struct {
|
||||
Apicontext string `json:"apicontext"`
|
||||
}
|
||||
|
||||
const Maxwidth = 10
|
||||
const Maxwidth = 12
|
||||
|
||||
/*
|
||||
Create a new request object for outgoing queries
|
||||
*/
|
||||
func Setup(c *cfg.Config, path string) *Request {
|
||||
client := req.C()
|
||||
if c.Debug {
|
||||
@@ -86,9 +90,12 @@ func Setup(c *cfg.Config, path string) *Request {
|
||||
}
|
||||
|
||||
return &Request{Url: c.Endpoint + path, R: R}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Iterate over args, considering the elements are filenames, and add
|
||||
them to the request.
|
||||
*/
|
||||
func GatherFiles(rq *Request, args []string) error {
|
||||
for _, file := range args {
|
||||
info, err := os.Stat(file)
|
||||
@@ -120,9 +127,48 @@ func GatherFiles(rq *Request, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func UploadFiles(c *cfg.Config, args []string) error {
|
||||
/*
|
||||
Check HTTP Response Code and validate JSON status output, if
|
||||
any. Turns'em into a regular error
|
||||
*/
|
||||
func HandleResponse(c *cfg.Config, resp *req.Response) error {
|
||||
// we expect a json response, extract the error, if any
|
||||
r := Response{}
|
||||
|
||||
if c.Debug {
|
||||
trace := resp.Request.TraceInfo()
|
||||
fmt.Println(trace.Blame())
|
||||
fmt.Println("----------")
|
||||
fmt.Println(trace)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(resp.String()), &r); err != nil {
|
||||
// text output!
|
||||
r.Message = resp.String()
|
||||
}
|
||||
|
||||
if !resp.IsSuccessState() {
|
||||
return fmt.Errorf("bad response: %s (%s)", resp.Status, r.Message)
|
||||
}
|
||||
|
||||
if !r.Success {
|
||||
if len(r.Message) == 0 {
|
||||
if resp.Err != nil {
|
||||
return resp.Err
|
||||
} else {
|
||||
return errors.New("Unknown error")
|
||||
}
|
||||
} else {
|
||||
return errors.New(r.Message)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func UploadFiles(w io.Writer, c *cfg.Config, args []string) error {
|
||||
// setup url, req.Request, timeout handling etc
|
||||
rq := Setup(c, "/file/")
|
||||
rq := Setup(c, "/uploads")
|
||||
|
||||
// collect files to upload from @argv
|
||||
if err := GatherFiles(rq, args); err != nil {
|
||||
@@ -150,47 +196,15 @@ func UploadFiles(c *cfg.Config, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return RespondExtended(resp)
|
||||
if err := HandleResponse(c, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return RespondExtended(w, resp)
|
||||
}
|
||||
|
||||
func HandleResponse(c *cfg.Config, resp *req.Response) error {
|
||||
// we expect a json response, extract the error, if any
|
||||
r := Response{}
|
||||
|
||||
if err := json.Unmarshal([]byte(resp.String()), &r); err != nil {
|
||||
// text output!
|
||||
r.Message = resp.String()
|
||||
}
|
||||
|
||||
if c.Debug {
|
||||
trace := resp.Request.TraceInfo()
|
||||
fmt.Println(trace.Blame())
|
||||
fmt.Println("----------")
|
||||
fmt.Println(trace)
|
||||
}
|
||||
|
||||
if !r.Success {
|
||||
if len(r.Message) == 0 {
|
||||
if resp.Err != nil {
|
||||
return resp.Err
|
||||
} else {
|
||||
return errors.New("Unknown error")
|
||||
}
|
||||
} else {
|
||||
return errors.New(r.Message)
|
||||
}
|
||||
}
|
||||
|
||||
// all right
|
||||
if r.Message != "" {
|
||||
fmt.Println(r.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func List(c *cfg.Config, args []string) error {
|
||||
rq := Setup(c, "/list/")
|
||||
func List(w io.Writer, c *cfg.Config, args []string) error {
|
||||
rq := Setup(c, "/uploads")
|
||||
|
||||
params := &ListParams{Apicontext: c.Apicontext}
|
||||
resp, err := rq.R.
|
||||
@@ -201,12 +215,16 @@ func List(c *cfg.Config, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return RespondTable(resp)
|
||||
if err := HandleResponse(c, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return UploadsRespondTable(w, resp)
|
||||
}
|
||||
|
||||
func Delete(c *cfg.Config, args []string) error {
|
||||
func Delete(w io.Writer, c *cfg.Config, args []string) error {
|
||||
for _, id := range args {
|
||||
rq := Setup(c, "/file/"+id+"/")
|
||||
rq := Setup(c, "/uploads/"+id+"/")
|
||||
|
||||
resp, err := rq.R.Delete(rq.Url)
|
||||
|
||||
@@ -218,47 +236,67 @@ func Delete(c *cfg.Config, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Upload %s successfully deleted.\n", id)
|
||||
fmt.Fprintf(w, "Upload %s successfully deleted.\n", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Describe(c *cfg.Config, args []string) error {
|
||||
func Describe(w io.Writer, c *cfg.Config, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("No id provided!")
|
||||
}
|
||||
|
||||
id := args[0] // we describe only 1 object
|
||||
|
||||
rq := Setup(c, "/upload/"+id+"/")
|
||||
rq := Setup(c, "/uploads/"+id)
|
||||
resp, err := rq.R.Get(rq.Url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return RespondExtended(resp)
|
||||
}
|
||||
|
||||
func Download(c *cfg.Config, args []string) error {
|
||||
id := args[0]
|
||||
|
||||
// progres bar
|
||||
bar := progressbar.Default(100)
|
||||
|
||||
callback := func(info req.DownloadInfo) {
|
||||
if info.Response.Response != nil {
|
||||
bar.Add(1)
|
||||
}
|
||||
if err := HandleResponse(c, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return RespondExtended(w, resp)
|
||||
}
|
||||
|
||||
func Download(w io.Writer, c *cfg.Config, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("No id provided!")
|
||||
}
|
||||
|
||||
id := args[0]
|
||||
|
||||
rq := Setup(c, "/uploads/"+id+"/file")
|
||||
|
||||
if !c.Silent {
|
||||
// progres bar
|
||||
bar := progressbar.Default(100)
|
||||
|
||||
callback := func(info req.DownloadInfo) {
|
||||
if info.Response.Response != nil {
|
||||
bar.Add(1)
|
||||
}
|
||||
}
|
||||
|
||||
rq.R.SetDownloadCallback(callback)
|
||||
}
|
||||
|
||||
rq := Setup(c, "/file/"+id+"/")
|
||||
resp, err := rq.R.
|
||||
SetOutputFile(id).
|
||||
SetDownloadCallback(callback).
|
||||
Get(rq.Url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.IsSuccessState() {
|
||||
return fmt.Errorf("bad response: %s", resp.Status)
|
||||
}
|
||||
|
||||
_, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition"))
|
||||
if err != nil {
|
||||
os.Remove(id)
|
||||
@@ -278,7 +316,34 @@ func Download(c *cfg.Config, args []string) error {
|
||||
return fmt.Errorf("\nUnable to rename file: " + err.Error())
|
||||
}
|
||||
|
||||
fmt.Printf("%s successfully downloaded to file %s.", id, cleanfilename)
|
||||
fmt.Fprintf(w, "%s successfully downloaded to file %s.", id, cleanfilename)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/**** Forms stuff ****/
|
||||
func CreateForm(w io.Writer, c *cfg.Config) error {
|
||||
// setup url, req.Request, timeout handling etc
|
||||
rq := Setup(c, "/forms")
|
||||
|
||||
// actual post w/ settings
|
||||
resp, err := rq.R.
|
||||
SetFormData(map[string]string{
|
||||
"expire": c.Expire,
|
||||
"description": c.Description,
|
||||
"notify": c.Notify,
|
||||
}).
|
||||
Post(rq.Url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := HandleResponse(c, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return RespondExtended(w, resp)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,37 +19,85 @@ package lib
|
||||
|
||||
import (
|
||||
//"github.com/alecthomas/repr"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/jarcoal/httpmock"
|
||||
"github.com/tlinden/cenophane/upctl/cfg"
|
||||
"github.com/tlinden/ephemerup/upctl/cfg"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const endpoint string = "http://localhost:8080/api/v1"
|
||||
const endpoint string = "http://localhost:8080/v1"
|
||||
|
||||
type Unit struct {
|
||||
name string
|
||||
apikey string // set to something else than "token" to fail auth
|
||||
wantfail bool // true: expect to fail
|
||||
files []string // path relative to ./t/
|
||||
sendcode int // for httpmock
|
||||
sendjson string // struct to respond with
|
||||
route string // dito
|
||||
method string // method to use
|
||||
expect string // regex used to parse the output
|
||||
|
||||
sendcode int // for httpmock
|
||||
sendjson string // struct to respond with
|
||||
sendfile string // bare file content to be sent
|
||||
route string // dito
|
||||
method string // method to use
|
||||
}
|
||||
|
||||
// simulate our cenophane server
|
||||
// simulate our ephemerup server
|
||||
func Intercept(tt Unit) {
|
||||
httpmock.RegisterResponder(tt.method, endpoint+tt.route,
|
||||
func(request *http.Request) (*http.Response, error) {
|
||||
respbody := fmt.Sprintf(tt.sendjson)
|
||||
resp := httpmock.NewStringResponse(tt.sendcode, respbody)
|
||||
resp.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||
var resp *http.Response
|
||||
|
||||
if tt.sendfile != "" {
|
||||
// simulate a file download
|
||||
content, err := ioutil.ReadFile(tt.sendfile)
|
||||
if err != nil {
|
||||
panic(err) // should not happen
|
||||
}
|
||||
|
||||
stat, err := os.Stat(tt.sendfile)
|
||||
if err != nil {
|
||||
panic(err) // should not happen as well
|
||||
}
|
||||
|
||||
resp = httpmock.NewStringResponse(tt.sendcode, string(content))
|
||||
resp.Header.Set("Content-Type", "text/markdown; charset=utf-8")
|
||||
resp.Header.Set("Content-Length", strconv.Itoa(int(stat.Size())))
|
||||
resp.Header.Set("Content-Disposition", "attachment; filename='t1'")
|
||||
} else {
|
||||
// simulate JSON response
|
||||
resp = httpmock.NewStringResponse(tt.sendcode, tt.sendjson)
|
||||
resp.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
})
|
||||
}
|
||||
|
||||
// execute the actual test
|
||||
func Check(t *testing.T, tt Unit, w *bytes.Buffer, err error) {
|
||||
testname := fmt.Sprintf("%s-%t", tt.name, tt.wantfail)
|
||||
|
||||
if err != nil && !tt.wantfail {
|
||||
t.Errorf("%s failed! wantfail: %t, error: %s", testname, tt.wantfail, err.Error())
|
||||
}
|
||||
|
||||
if tt.expect != "" {
|
||||
got := strings.TrimSpace(w.String())
|
||||
r := regexp.MustCompile(tt.expect)
|
||||
if !r.MatchString(got) {
|
||||
t.Errorf("%s failed! error: output does not match!\nexpect: %s\ngot:\n%s", testname, tt.expect, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUploadFiles(t *testing.T) {
|
||||
conf := &cfg.Config{
|
||||
Mock: true,
|
||||
@@ -63,42 +111,67 @@ func TestUploadFiles(t *testing.T) {
|
||||
name: "upload-file",
|
||||
apikey: "token",
|
||||
wantfail: false,
|
||||
route: "/file/",
|
||||
route: "/uploads",
|
||||
sendcode: 200,
|
||||
sendjson: `{"success": true}`,
|
||||
files: []string{"../t/t1"}, // pwd is lib/ !
|
||||
method: "POST",
|
||||
},
|
||||
{
|
||||
name: "upload-nonexistent-file",
|
||||
name: "upload-dir",
|
||||
apikey: "token",
|
||||
wantfail: false,
|
||||
route: "/uploads",
|
||||
sendcode: 200,
|
||||
sendjson: `{"success": true}`,
|
||||
files: []string{"../t"}, // pwd is lib/ !
|
||||
method: "POST",
|
||||
},
|
||||
{
|
||||
name: "upload-catch-nonexistent-file",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/file/",
|
||||
route: "/uploads",
|
||||
sendcode: 200,
|
||||
sendjson: `{"success": false}`,
|
||||
files: []string{"../t/none"},
|
||||
method: "POST",
|
||||
},
|
||||
{
|
||||
name: "upload-unauth",
|
||||
name: "upload-catch-no-access",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/file/",
|
||||
route: "/uploads",
|
||||
sendcode: 403,
|
||||
sendjson: `{"success": false}`,
|
||||
files: []string{"../t/t1"},
|
||||
method: "POST",
|
||||
},
|
||||
{
|
||||
name: "upload-check-output",
|
||||
apikey: "token",
|
||||
wantfail: false,
|
||||
route: "/uploads",
|
||||
sendcode: 200,
|
||||
sendjson: `{"uploads":[
|
||||
{
|
||||
"id":"cc2c965a","expire":"asap","file":"t1","members":["t1"],
|
||||
"uploaded":1679396814.890502,"context":"foo","url":""
|
||||
}
|
||||
],
|
||||
"success":true,
|
||||
"message":"Download url: http://localhost:8080/download/cc2c965a/t1",
|
||||
"code":200}`,
|
||||
files: []string{"../t/t1"}, // pwd is lib/ !
|
||||
method: "POST",
|
||||
expect: "Expire: On first access",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testname := fmt.Sprintf("UploadFiles-%s-%t", tt.name, tt.wantfail)
|
||||
Intercept(tt)
|
||||
err := UploadFiles(conf, tt.files)
|
||||
|
||||
if err != nil && !tt.wantfail {
|
||||
t.Errorf("%s failed! wantfail: %t, error: %s", testname, tt.wantfail, err.Error())
|
||||
}
|
||||
for _, unit := range tests {
|
||||
var w bytes.Buffer
|
||||
Intercept(unit)
|
||||
Check(t, unit, &w, UploadFiles(&w, conf, unit.files))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,28 +183,227 @@ func TestList(t *testing.T) {
|
||||
Silent: true,
|
||||
}
|
||||
|
||||
listing := `{"uploads":[{"id":"c8dh","expire":"asap","file":"t1","members":["t1"],"uploaded":1679318969.6434112,"context":"foo","url":""}],"success":true,"message":"","code":200}`
|
||||
listing := `{"uploads":[
|
||||
{
|
||||
"id":"cc2c965a","expire":"asap","file":"t1","members":["t1"],
|
||||
"uploaded":1679396814.890502,"context":"foo","url":""
|
||||
}
|
||||
],
|
||||
"success":true,
|
||||
"message":"",
|
||||
"code":200}`
|
||||
|
||||
listingnoaccess := `{"success":false,"message":"invalid context","code":503}`
|
||||
|
||||
tests := []Unit{
|
||||
{
|
||||
name: "list",
|
||||
apikey: "token",
|
||||
wantfail: false,
|
||||
route: "/list/",
|
||||
route: "/uploads",
|
||||
sendcode: 200,
|
||||
sendjson: listing,
|
||||
files: []string{},
|
||||
method: "GET",
|
||||
expect: `cc2c965a\s*asap\s*foo\s*2023-03-21 12:06:54`, // expect tabular output
|
||||
},
|
||||
{
|
||||
name: "list-catch-empty-json",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads",
|
||||
sendcode: 404,
|
||||
sendjson: "",
|
||||
files: []string{},
|
||||
method: "GET",
|
||||
},
|
||||
{
|
||||
name: "list-catch-no-access",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads",
|
||||
sendcode: 503,
|
||||
sendjson: listingnoaccess,
|
||||
files: []string{},
|
||||
method: "GET",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testname := fmt.Sprintf("List-%s-%t", tt.name, tt.wantfail)
|
||||
Intercept(tt)
|
||||
err := List(conf, []string{})
|
||||
for _, unit := range tests {
|
||||
var w bytes.Buffer
|
||||
Intercept(unit)
|
||||
Check(t, unit, &w, List(&w, conf, []string{}))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil && !tt.wantfail {
|
||||
t.Errorf("%s failed! wantfail: %t, error: %s", testname, tt.wantfail, err.Error())
|
||||
}
|
||||
func TestDescribe(t *testing.T) {
|
||||
conf := &cfg.Config{
|
||||
Mock: true,
|
||||
Apikey: "token",
|
||||
Endpoint: endpoint,
|
||||
Silent: true,
|
||||
}
|
||||
|
||||
listing := `{"uploads":[
|
||||
{
|
||||
"id":"cc2c965a","expire":"asap","file":"t1","members":["t1"],
|
||||
"uploaded":1679396814.890502,"context":"foo","url":""
|
||||
}
|
||||
],
|
||||
"success":true,
|
||||
"message":"",
|
||||
"code":200}`
|
||||
|
||||
listingnoaccess := `{"success":false,"message":"invalid context","code":503}`
|
||||
|
||||
tests := []Unit{
|
||||
{
|
||||
name: "describe",
|
||||
apikey: "token",
|
||||
wantfail: false,
|
||||
route: "/uploads/",
|
||||
sendcode: 200,
|
||||
sendjson: listing,
|
||||
files: []string{"cc2c965a"},
|
||||
method: "GET",
|
||||
expect: `Created: 2023-03-21 12:06:54.890501888`,
|
||||
},
|
||||
{
|
||||
name: "describe-catch-empty-json",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads/",
|
||||
sendcode: 200,
|
||||
sendjson: "",
|
||||
files: []string{"cc2c965a"},
|
||||
method: "GET",
|
||||
},
|
||||
{
|
||||
name: "describe-catch-no-access",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads/",
|
||||
sendcode: 503,
|
||||
sendjson: listingnoaccess,
|
||||
files: []string{"cc2c965a"},
|
||||
method: "GET",
|
||||
},
|
||||
}
|
||||
|
||||
for _, unit := range tests {
|
||||
var w bytes.Buffer
|
||||
unit.route += unit.files[0]
|
||||
Intercept(unit)
|
||||
Check(t, unit, &w, Describe(&w, conf, unit.files))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
conf := &cfg.Config{
|
||||
Mock: true,
|
||||
Apikey: "token",
|
||||
Endpoint: endpoint,
|
||||
Silent: true,
|
||||
}
|
||||
|
||||
listingnoaccess := `{"success":false,"message":"invalid context","code":503}`
|
||||
|
||||
tests := []Unit{
|
||||
{
|
||||
name: "delete",
|
||||
apikey: "token",
|
||||
wantfail: false,
|
||||
route: "/uploads/",
|
||||
sendcode: 200,
|
||||
sendjson: `{"success":true,"message":"","code":200}`,
|
||||
files: []string{"cc2c965a"},
|
||||
method: "DELETE",
|
||||
expect: `Upload cc2c965a successfully deleted`,
|
||||
},
|
||||
{
|
||||
name: "delete-catch-empty-json",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads/",
|
||||
sendcode: 200,
|
||||
sendjson: "",
|
||||
files: []string{"cc2c965a"},
|
||||
method: "DELETE",
|
||||
},
|
||||
{
|
||||
name: "delete-catch-no-access",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads/",
|
||||
sendcode: 503,
|
||||
sendjson: listingnoaccess,
|
||||
files: []string{"cc2c965a"},
|
||||
method: "DELETE",
|
||||
},
|
||||
}
|
||||
|
||||
for _, unit := range tests {
|
||||
var w bytes.Buffer
|
||||
unit.route += unit.files[0] + "/"
|
||||
Intercept(unit)
|
||||
Check(t, unit, &w, Delete(&w, conf, unit.files))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownload(t *testing.T) {
|
||||
conf := &cfg.Config{
|
||||
Mock: true,
|
||||
Apikey: "token",
|
||||
Endpoint: endpoint,
|
||||
Silent: true,
|
||||
}
|
||||
|
||||
listingnoaccess := `{"success":false,"message":"invalid context","code":503}`
|
||||
|
||||
tests := []Unit{
|
||||
{
|
||||
name: "download",
|
||||
apikey: "token",
|
||||
wantfail: false,
|
||||
route: "/uploads/",
|
||||
sendcode: 200,
|
||||
sendfile: "../t/t1",
|
||||
files: []string{"cc2c965a"},
|
||||
method: "GET",
|
||||
expect: `cc2c965a successfully downloaded to file t1`,
|
||||
},
|
||||
{
|
||||
name: "download-catch-empty-response",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads/",
|
||||
sendcode: 200,
|
||||
files: []string{"cc2c965a"},
|
||||
method: "GET",
|
||||
},
|
||||
{
|
||||
name: "download-catch-no-access",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads/",
|
||||
sendcode: 503,
|
||||
sendjson: listingnoaccess,
|
||||
files: []string{"cc2c965a"},
|
||||
method: "GET",
|
||||
},
|
||||
}
|
||||
|
||||
for _, unit := range tests {
|
||||
var w bytes.Buffer
|
||||
unit.route += unit.files[0] + "/file"
|
||||
Intercept(unit)
|
||||
Check(t, unit, &w, Download(&w, conf, unit.files))
|
||||
|
||||
if unit.sendfile != "" {
|
||||
file := filepath.Base(unit.sendfile)
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
os.Remove(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,9 @@ import (
|
||||
"fmt"
|
||||
"github.com/imroc/req/v3"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/tlinden/cenophane/common"
|
||||
"os"
|
||||
"github.com/tlinden/ephemerup/common"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -34,15 +35,17 @@ func prepareExpire(expire string, start common.Timestamp) string {
|
||||
case "asap":
|
||||
return "On first access"
|
||||
default:
|
||||
return time.Unix(start.Unix()+int64(common.Duration2int(expire)), 0).Format("2006-01-02 15:04:05")
|
||||
return time.Unix(start.Unix()+int64(common.Duration2int(expire)), 0).
|
||||
Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// generic table writer
|
||||
func WriteTable(headers []string, data [][]string) {
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
func WriteTable(w io.Writer, headers []string, data [][]string) {
|
||||
tableString := &strings.Builder{}
|
||||
table := tablewriter.NewWriter(tableString)
|
||||
|
||||
table.SetHeader(headers)
|
||||
table.AppendBulk(data)
|
||||
@@ -60,76 +63,95 @@ func WriteTable(headers []string, data [][]string) {
|
||||
table.SetNoWhiteSpace(true)
|
||||
|
||||
table.Render()
|
||||
|
||||
fmt.Fprintln(w, tableString.String())
|
||||
}
|
||||
|
||||
// output like psql \x
|
||||
func WriteExtended(uploads *common.Uploads) {
|
||||
/* Print output like psql \x
|
||||
|
||||
Prints all Uploads and Forms which exist in common.Response,
|
||||
however, we expect only one kind of them to be actually filled, so
|
||||
the function can be used for forms and uploads.
|
||||
*/
|
||||
func WriteExtended(w io.Writer, response *common.Response) {
|
||||
format := fmt.Sprintf("%%%ds: %%s\n", Maxwidth)
|
||||
|
||||
// we shall only have 1 element, however, if we ever support more, here we go
|
||||
for _, entry := range uploads.Entries {
|
||||
expire := prepareExpire(entry.Expire, entry.Uploaded)
|
||||
fmt.Printf(format, "Id", entry.Id)
|
||||
fmt.Printf(format, "Expire", expire)
|
||||
fmt.Printf(format, "Context", entry.Context)
|
||||
fmt.Printf(format, "Uploaded", entry.Uploaded)
|
||||
fmt.Printf(format, "Filename", entry.File)
|
||||
fmt.Printf(format, "Url", entry.Url)
|
||||
fmt.Println()
|
||||
for _, entry := range response.Uploads {
|
||||
expire := prepareExpire(entry.Expire, entry.Created)
|
||||
fmt.Fprintf(w, format, "Id", entry.Id)
|
||||
fmt.Fprintf(w, format, "Expire", expire)
|
||||
fmt.Fprintf(w, format, "Context", entry.Context)
|
||||
fmt.Fprintf(w, format, "Created", entry.Created)
|
||||
fmt.Fprintf(w, format, "Filename", entry.File)
|
||||
fmt.Fprintf(w, format, "Url", entry.Url)
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
|
||||
for _, entry := range response.Forms {
|
||||
expire := prepareExpire(entry.Expire, entry.Created)
|
||||
fmt.Fprintf(w, format, "Id", entry.Id)
|
||||
fmt.Fprintf(w, format, "Expire", expire)
|
||||
fmt.Fprintf(w, format, "Context", entry.Context)
|
||||
fmt.Fprintf(w, format, "Created", entry.Created)
|
||||
fmt.Fprintf(w, format, "Description", entry.Description)
|
||||
fmt.Fprintf(w, format, "Notify", entry.Notify)
|
||||
fmt.Fprintf(w, format, "Url", entry.Url)
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
}
|
||||
|
||||
// extract an common.Uploads{} struct from json response
|
||||
func GetUploadsFromResponse(resp *req.Response) (*common.Uploads, error) {
|
||||
uploads := common.Uploads{}
|
||||
func GetResponse(resp *req.Response) (*common.Response, error) {
|
||||
response := common.Response{}
|
||||
|
||||
if err := json.Unmarshal([]byte(resp.String()), &uploads); err != nil {
|
||||
if err := json.Unmarshal([]byte(resp.String()), &response); err != nil {
|
||||
return nil, errors.New("Could not unmarshall JSON response: " + err.Error())
|
||||
}
|
||||
|
||||
if !uploads.Success {
|
||||
return nil, errors.New(uploads.Message)
|
||||
if !response.Success {
|
||||
return nil, errors.New(response.Message)
|
||||
}
|
||||
|
||||
return &uploads, nil
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// turn the Uploads{} struct into a table and print it
|
||||
func RespondTable(resp *req.Response) error {
|
||||
uploads, err := GetUploadsFromResponse(resp)
|
||||
func UploadsRespondTable(w io.Writer, resp *req.Response) error {
|
||||
response, err := GetResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if uploads.Message != "" {
|
||||
fmt.Println(uploads.Message)
|
||||
if response.Message != "" {
|
||||
fmt.Fprintln(w, response.Message)
|
||||
}
|
||||
|
||||
// tablewriter
|
||||
data := [][]string{}
|
||||
for _, entry := range uploads.Entries {
|
||||
for _, entry := range response.Uploads {
|
||||
data = append(data, []string{
|
||||
entry.Id, entry.Expire, entry.Context, entry.Uploaded.Format("2006-01-02 15:04:05"),
|
||||
entry.Id, entry.Expire, entry.Context, entry.Created.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
WriteTable([]string{"ID", "EXPIRE", "CONTEXT", "UPLOADED"}, data)
|
||||
WriteTable(w, []string{"ID", "EXPIRE", "CONTEXT", "CREATED"}, data)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// turn the Uploads{} struct into xtnd output and print it
|
||||
func RespondExtended(resp *req.Response) error {
|
||||
uploads, err := GetUploadsFromResponse(resp)
|
||||
func RespondExtended(w io.Writer, resp *req.Response) error {
|
||||
response, err := GetResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if uploads.Message != "" {
|
||||
fmt.Println(uploads.Message)
|
||||
if response.Message != "" {
|
||||
fmt.Fprintln(w, response.Message)
|
||||
}
|
||||
|
||||
WriteExtended(uploads)
|
||||
WriteExtended(w, response)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user