github.com/release-engineering/exodus-rsync@v1.11.2/internal/gw/gw_helpers_test.go (about)

     1  package gw
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/release-engineering/exodus-rsync/internal/args"
    13  	"github.com/release-engineering/exodus-rsync/internal/log"
    14  )
    15  
    16  // A fake for the exodus-gw service.
    17  type fakeGw struct {
    18  	t *testing.T
    19  
    20  	// List of IDs of publish objects to be created, or empty if creating publishes
    21  	// should fail
    22  	createPublishIds []string
    23  
    24  	// Existing publish objects
    25  	publishes publishMap
    26  
    27  	// If non-nil, forces next HTTP request to return this error.
    28  	nextHTTPError error
    29  
    30  	// If non-nil, forces next HTTP request to return this response
    31  	nextHTTPResponse *http.Response
    32  }
    33  
    34  type publishMap map[string]*fakePublish
    35  
    36  type fakePublish struct {
    37  	id         string
    38  	items      []ItemInput
    39  	lastCommit string
    40  
    41  	// If publish is committed, then each time the task state is polled,
    42  	// we'll pop the next state from here.
    43  	taskStates []string
    44  }
    45  
    46  func (p *fakePublish) nextState() string {
    47  	out := p.taskStates[0]
    48  	p.taskStates = p.taskStates[1:]
    49  	return out
    50  }
    51  
    52  // Implement RoundTripper interface for fake handling of HTTP requests.
    53  func (f *fakeGw) RoundTrip(r *http.Request) (*http.Response, error) {
    54  	if r.Body != nil {
    55  		defer r.Body.Close()
    56  	}
    57  
    58  	if f.nextHTTPError != nil {
    59  		err := f.nextHTTPError
    60  		f.nextHTTPError = nil
    61  		return nil, err
    62  	}
    63  
    64  	if f.nextHTTPResponse != nil {
    65  		out := f.nextHTTPResponse
    66  		f.nextHTTPResponse = nil
    67  		return out, nil
    68  	}
    69  
    70  	out := &http.Response{}
    71  
    72  	out.Status = "404 Not Found"
    73  	out.StatusCode = 404
    74  	// Body must not be nil, even for empty response.
    75  	out.Body = io.NopCloser(strings.NewReader(""))
    76  
    77  	path := strings.TrimPrefix(r.URL.Path, "/")
    78  	route := strings.Split(path, "/")
    79  
    80  	if len(route) == 2 && route[0] == "task" && r.Method == "GET" {
    81  		return f.getTask(route[1]), nil
    82  	}
    83  
    84  	// For every other route, path must be under /env/ suffix, bail out
    85  	// early if not
    86  	if route[0] != "env" {
    87  		f.t.Logf("unexpected request path %v", path)
    88  		return out, nil
    89  	}
    90  	route = route[1:]
    91  
    92  	if len(route) == 1 && route[0] == "publish" && r.Method == "POST" {
    93  		return f.createPublish(), nil
    94  	}
    95  
    96  	if len(route) == 2 && route[0] == "publish" && r.Method == "GET" {
    97  		return f.getPublish(route[1]), nil
    98  	}
    99  
   100  	if len(route) == 2 && route[0] == "publish" && r.Method == "PUT" {
   101  		return f.addPublishItems(r, route[1]), nil
   102  	}
   103  
   104  	if len(route) == 3 && route[0] == "publish" && route[2] == "commit" && r.Method == "POST" {
   105  		return f.commitPublish(route[1], r.URL.Query().Get("commit_mode")), nil
   106  	}
   107  
   108  	return out, nil
   109  }
   110  
   111  func newFakeGw(t *testing.T, c *client) *fakeGw {
   112  	out := &fakeGw{t: t, createPublishIds: make([]string, 0), publishes: make(publishMap)}
   113  	out.install(c)
   114  	return out
   115  }
   116  
   117  func (f *fakeGw) install(c *client) {
   118  	ctx := log.NewContext(context.Background(), log.Package.NewLogger(args.Config{}))
   119  	c.httpClient.Transport = retryTransport(ctx, c.cfg, f)
   120  }
   121  
   122  func (f *fakeGw) createPublish() *http.Response {
   123  	out := &http.Response{}
   124  
   125  	if len(f.createPublishIds) == 0 {
   126  		f.t.Log("out of publish IDs!")
   127  		out.Status = "500 Internal Server Error"
   128  		out.StatusCode = 500
   129  		return out
   130  	}
   131  
   132  	id := f.createPublishIds[0]
   133  	f.createPublishIds = f.createPublishIds[1:]
   134  
   135  	newPublish := &fakePublish{}
   136  	newPublish.id = id
   137  	// By default, any new publish will successfully commit.
   138  	newPublish.taskStates = []string{"NOT_STARTED", "IN_PROGRESS", "COMPLETE"}
   139  
   140  	f.publishes[id] = newPublish
   141  
   142  	content := fmt.Sprintf(`{
   143  		"id": "%s",
   144  		"env": "env",
   145  		"state": "PENDING",
   146  		"links": {
   147  			"self": "/env/publish/%[1]s",
   148  			"commit": "/env/publish/%[1]s/commit"
   149  		},
   150  		"items": []
   151  	}`, id)
   152  	out.Body = io.NopCloser(strings.NewReader(content))
   153  
   154  	out.Header = http.Header{}
   155  	out.Header.Add("Content-Type", "application/json")
   156  	out.Status = "200 OK"
   157  	out.StatusCode = 200
   158  
   159  	return out
   160  }
   161  
   162  func (f *fakeGw) addPublishItems(r *http.Request, id string) *http.Response {
   163  	out := &http.Response{}
   164  
   165  	publish, havePublish := f.publishes[id]
   166  	if !havePublish {
   167  		f.t.Logf("requested nonexistent publish %s", id)
   168  		out.Status = "404 Not Found"
   169  		out.StatusCode = 404
   170  		return out
   171  	}
   172  
   173  	dec := json.NewDecoder(r.Body)
   174  	requestItems := make([]map[string]string, 0)
   175  
   176  	err := dec.Decode(&requestItems)
   177  	if err != nil {
   178  		f.t.Logf("non-JSON request body? err = %v", err)
   179  		out.Status = "400 Bad Request"
   180  		out.StatusCode = 400
   181  		return out
   182  	}
   183  
   184  	for _, item := range requestItems {
   185  		publish.items = append(publish.items, ItemInput{item["web_uri"], item["object_key"], item["content_type"], item["link_to"]})
   186  	}
   187  
   188  	out.Status = "200 OK"
   189  	out.StatusCode = 200
   190  	out.Body = io.NopCloser(strings.NewReader("{}"))
   191  	return out
   192  }
   193  
   194  func (f *fakeGw) commitPublish(id string, mode string) *http.Response {
   195  	out := &http.Response{}
   196  
   197  	publish, havePublish := f.publishes[id]
   198  	if !havePublish {
   199  		f.t.Logf("requested nonexistent publish %s", id)
   200  		out.Status = "404 Not Found"
   201  		out.StatusCode = 404
   202  		return out
   203  	}
   204  
   205  	if len(publish.taskStates) == 0 {
   206  		// test can set taskStates empty to force an error
   207  		out.Status = "500 Internal Server Error"
   208  		out.StatusCode = 500
   209  		return out
   210  	}
   211  
   212  	publish.lastCommit = mode
   213  
   214  	state := publish.nextState()
   215  	taskID := "task-" + publish.id
   216  	content := fmt.Sprintf(`{
   217  		"id": "%s",
   218  		"publish_id": "%s",
   219  		"state": "%s",
   220  		"links": {
   221  			"self": "/task/%[1]s"
   222  		}
   223  	}`, taskID, id, state)
   224  
   225  	out.Status = "200 OK"
   226  	out.StatusCode = 200
   227  	out.Body = io.NopCloser(strings.NewReader(content))
   228  	return out
   229  }
   230  
   231  func (f *fakeGw) getTask(id string) *http.Response {
   232  	out := &http.Response{}
   233  
   234  	if !strings.HasPrefix(id, "task-") {
   235  		f.t.Logf("requested non-task ID %s", id)
   236  		out.Status = "404 Not Found"
   237  		out.StatusCode = 404
   238  		return out
   239  	}
   240  
   241  	publishID := strings.TrimPrefix(id, "task-")
   242  	publish := f.publishes[publishID]
   243  	if len(publish.taskStates) == 0 {
   244  		out.Status = "500 Internal Server Error"
   245  		out.StatusCode = 500
   246  		return out
   247  	}
   248  
   249  	state := publish.nextState()
   250  	content := fmt.Sprintf(`{
   251  		"id": "%s",
   252  		"publish_id": "%s",
   253  		"state": "%s",
   254  		"links": {
   255  			"self": "/task/%[1]s"
   256  		}
   257  	}`, id, publishID, state)
   258  
   259  	out.Status = "200 OK"
   260  	out.StatusCode = 200
   261  	out.Body = io.NopCloser(strings.NewReader(content))
   262  	return out
   263  }
   264  
   265  func (f *fakeGw) getPublish(id string) *http.Response {
   266  	out := &http.Response{}
   267  
   268  	_, havePublish := f.publishes[id]
   269  	if !havePublish {
   270  		f.t.Logf("requested nonexistent publish %s", id)
   271  		out.Status = "404 Not Found"
   272  		out.StatusCode = 404
   273  		return out
   274  	}
   275  
   276  	content := fmt.Sprintf(`{
   277  		"id": "%s",
   278  		"env": "env",
   279  		"links": {
   280  			"self": "/env/publish/%[1]s",
   281  			"commit": "/env/publish/%[1]s/commit"
   282  		},
   283  		"items": []
   284  	}`, id)
   285  
   286  	out.Status = "200 OK"
   287  	out.StatusCode = 200
   288  	out.Body = io.NopCloser(strings.NewReader(content))
   289  
   290  	return out
   291  }