feat: add http roundtripper mock and deterministic integration tests for allanime

This commit is contained in:
2026-06-16 10:59:26 +02:00
committed by Milas Holsting
parent 0d1ae305b5
commit 06b50509e8
2 changed files with 820 additions and 0 deletions

View File

@@ -0,0 +1,797 @@
package allanime
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"strings"
"testing"
)
func TestGraphqlRequest_SuccessAndHeaders(t *testing.T) {
t.Parallel()
var method, url, ct, referer, ua string
var bodyBuf bytes.Buffer
provider := &AllAnimeProvider{
httpClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
method = req.Method
url = req.URL.String()
ct = req.Header.Get("Content-Type")
referer = req.Header.Get("Referer")
ua = req.Header.Get("User-Agent")
_, _ = io.Copy(&bodyBuf, req.Body)
return mockStringResponse(http.StatusOK, `{"data":{"key":"val"}}`), nil
}),
},
}
_, err := provider.graphqlRequest(
context.Background(),
"query($id:String!){show(_id:$id){name}}",
map[string]any{"id": "abc"},
)
if err != nil {
t.Fatalf("graphqlRequest() error = %v", err)
}
verifyGraphqlRequest(t, method, url, ct, referer, ua, bodyBuf.Bytes())
}
func TestGraphqlRequest_Errors(t *testing.T) {
t.Parallel()
tests := []struct {
name string
status int
body string
}{
{
name: "graphql error in response",
status: http.StatusOK,
body: `{"errors":[{"message":"not found"}]}`,
},
{
name: "non-200 status",
status: http.StatusInternalServerError,
body: `{"data":{}}`,
},
{
name: "invalid json body",
status: http.StatusOK,
body: `not json`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
provider := &AllAnimeProvider{
httpClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(tt.status, tt.body), nil
}),
},
}
_, err := provider.graphqlRequest(
context.Background(),
"query($id:String!){show(_id:$id){name}}",
map[string]any{"id": "abc"},
)
if err == nil {
t.Error("expected error, got nil")
}
})
}
}
func verifyGraphqlRequest(t *testing.T, method, url, ct, referer, ua string, body []byte) {
t.Helper()
if method != http.MethodPost {
t.Errorf("method = %q, want POST", method)
}
if url != allAnimeBaseURL+"/api" {
t.Errorf("url = %q, want %q", url, allAnimeBaseURL+"/api")
}
if ct != "application/json" {
t.Errorf("Content-Type = %q", ct)
}
if referer != allAnimeReferer {
t.Errorf("Referer = %q", referer)
}
if ua != defaultUserAgent {
t.Errorf("User-Agent = %q", ua)
}
var sent map[string]any
if err := json.Unmarshal(body, &sent); err != nil {
t.Fatalf("unmarshal sent body: %v", err)
}
if sent["query"] != "query($id:String!){show(_id:$id){name}}" {
t.Errorf("unexpected query in body")
}
vars, ok := sent["variables"].(map[string]any)
if !ok || vars["id"] != "abc" {
t.Errorf("unexpected variables in body")
}
}
func TestGraphqlRequest_SetsTranslationTypeLower(t *testing.T) {
t.Parallel()
provider := &AllAnimeProvider{
httpClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusOK, `{"data":{}}`), nil
}),
},
}
_, err := provider.graphqlRequest(
context.Background(),
"query($t:VaildTranslationTypeEnumType!){x(translationType:$t){id}}",
map[string]any{"translationType": "SUB"},
)
if err != nil {
t.Fatalf("graphqlRequest: %v", err)
}
}
func TestGraphqlRequestWithHash_Plain(t *testing.T) {
t.Parallel()
provider := &AllAnimeProvider{
utlsClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
if req.Method != http.MethodGet {
t.Errorf("method = %q, want GET", req.Method)
}
if !strings.Contains(req.URL.String(), episodeQueryHash) {
t.Errorf("url should contain hash, got %q", req.URL.String())
}
if req.Header.Get("Referer") != allAnimeReferer {
t.Errorf("Referer = %q", req.Header.Get("Referer"))
}
return mockStringResponse(http.StatusOK, `{"data":{"episode":{"sourceUrls":[{"sourceUrl":"https://example.test/v.mp4","sourceName":"default"}]}}}`), nil
}),
},
}
result, err := provider.graphqlRequestWithHash(
context.Background(),
"show123", "1", "sub",
)
if err != nil {
t.Fatalf("graphqlRequestWithHash: %v", err)
}
data, ok := result["data"].(map[string]any)
if !ok {
t.Fatal("result missing data key")
}
sources := nestedSlice(data, "episode", "sourceUrls")
if len(sources) != 1 {
t.Fatalf("got %d sources, want 1", len(sources))
}
}
func TestGraphqlRequestWithHash_Encrypted(t *testing.T) {
t.Parallel()
encryptedPayload := buildEncryptedTobeparsedPayload(t, []byte(`{"sourceUrls":[{"sourceUrl":"https://e.test/v.mp4","sourceName":"default"}]}`))
provider := &AllAnimeProvider{
utlsClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusOK, `{"data":{"tobeparsed":"`+encryptedPayload+`"}}`), nil
}),
},
}
result, err := provider.graphqlRequestWithHash(
context.Background(),
"show456", "2", "dub",
)
if err != nil {
t.Fatalf("graphqlRequestWithHash: %v", err)
}
sources := nestedSlice(result, "episode", "sourceUrls")
if len(sources) != 1 {
t.Fatalf("got %d sources, want 1", len(sources))
}
}
func TestGraphqlRequestWithHash_Non200(t *testing.T) {
t.Parallel()
provider := &AllAnimeProvider{
utlsClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusNotFound, `not found`), nil
}),
},
}
_, err := provider.graphqlRequestWithHash(
context.Background(),
"x", "1", "sub",
)
if err == nil {
t.Fatal("expected error for non-200")
}
}
func TestGraphqlRequestWithHash_EmptyData(t *testing.T) {
t.Parallel()
provider := &AllAnimeProvider{
utlsClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusOK, `{"data":{}}`), nil
}),
},
}
_, err := provider.graphqlRequestWithHash(
context.Background(),
"x", "1", "sub",
)
if err == nil {
t.Fatal("expected error for empty data")
}
}
func TestGetEpisodeSources_EncryptedHash(t *testing.T) {
t.Parallel()
encrypted := buildEncryptedTobeparsedPayload(t, []byte(`{"sourceUrls":[{"sourceUrl":"https://direct.test/v.mp4","sourceName":"default"}]}`))
provider := &AllAnimeProvider{
httpClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
t.Error("fallback POST should not be called")
return nil, nil
}),
},
utlsClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusOK, `{"data":{"tobeparsed":"`+encrypted+`"}}`), nil
}),
},
extractor: newProviderExtractor(),
}
sources, err := provider.GetEpisodeSources(context.Background(), "show1", "1", "sub")
if err != nil {
t.Fatalf("GetEpisodeSources: %v", err)
}
if len(sources) == 0 {
t.Fatal("expected at least one source")
}
if sources[0].URL != "https://direct.test/v.mp4" {
t.Errorf("URL = %q", sources[0].URL)
}
}
func TestGetEpisodeSources_FallbackPost(t *testing.T) {
t.Parallel()
sourceResponse := `{"data":{"episode":{"sourceUrls":[{"sourceUrl":"https://direct.test/v.mp4","sourceName":"default"}]}}}`
fallbackCalled := false
provider := &AllAnimeProvider{
httpClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
fallbackCalled = true
return mockStringResponse(http.StatusOK, sourceResponse), nil
}),
},
utlsClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusNotFound, `not found`), nil
}),
},
extractor: newProviderExtractor(),
}
sources, err := provider.GetEpisodeSources(context.Background(), "show3", "3", "sub")
if err != nil {
t.Fatalf("GetEpisodeSources: %v", err)
}
if !fallbackCalled {
t.Fatal("fallback POST was not called")
}
if len(sources) == 0 {
t.Fatal("expected at least one source")
}
}
func TestGetEpisodeSources_BothFail(t *testing.T) {
t.Parallel()
provider := &AllAnimeProvider{
httpClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusNotFound, `not found`), nil
}),
},
utlsClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusNotFound, `not found`), nil
}),
},
extractor: newProviderExtractor(),
}
_, err := provider.GetEpisodeSources(context.Background(), "show4", "4", "sub")
if err == nil {
t.Fatal("expected error when both requests fail")
}
}
func TestGetAvailableEpisodes(t *testing.T) {
t.Parallel()
tests := []struct {
name string
body string
wantSub int
wantDub int
wantErr bool
}{
{
name: "sub and dub available",
body: `{"data":{"show":{"availableEpisodesDetail":{"sub":["1","2","3"],"dub":["1"]},"lastEpisodeInfo":{}}}}`,
wantSub: 3,
wantDub: 1,
},
{
name: "sub only",
body: `{"data":{"show":{"availableEpisodesDetail":{"sub":["1","2"],"dub":null},"lastEpisodeInfo":{}}}}`,
wantSub: 2,
wantDub: 0,
},
{
name: "show not found",
body: `{"data":{"show":null}}`,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
provider := &AllAnimeProvider{
httpClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusOK, tt.body), nil
}),
},
}
available, err := provider.GetAvailableEpisodes(context.Background(), "showX")
if (err != nil) != tt.wantErr {
t.Fatalf("GetAvailableEpisodes() error = %v, wantErr %v", err, tt.wantErr)
}
if tt.wantErr {
return
}
if len(available.Sub) != tt.wantSub {
t.Errorf("Sub count = %d, want %d", len(available.Sub), tt.wantSub)
}
if len(available.Dub) != tt.wantDub {
t.Errorf("Dub count = %d, want %d", len(available.Dub), tt.wantDub)
}
})
}
}
func TestSearch(t *testing.T) {
t.Parallel()
t.Run("returns results", func(t *testing.T) {
t.Parallel()
provider := &AllAnimeProvider{
httpClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusOK, `{"data":{"shows":{"edges":[{"_id":"id1","malId":"1","name":"Title One"},{"_id":"id2","malId":"2","name":"Title Two"}]}}}`), nil
}),
},
}
results, err := provider.Search(context.Background(), "test", "sub")
if err != nil {
t.Fatalf("Search: %v", err)
}
if len(results) != 2 {
t.Fatalf("len = %d, want 2", len(results))
}
if results[0].ID != "id1" || results[0].MalID != "1" || results[0].Name != "Title One" {
t.Errorf("result[0] = %+v", results[0])
}
})
t.Run("empty results", func(t *testing.T) {
t.Parallel()
provider := &AllAnimeProvider{
httpClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusOK, `{"data":{"shows":{"edges":[]}}}`), nil
}),
},
}
results, err := provider.Search(context.Background(), "nonexistent", "sub")
if err != nil {
t.Fatalf("Search: %v", err)
}
if len(results) != 0 {
t.Errorf("len = %d, want 0", len(results))
}
})
}
func TestGetStreams_FullSuccess(t *testing.T) {
t.Parallel()
searchBody := `{"data":{"shows":{"edges":[{"_id":"show123","malId":"1","name":"Test Anime"}]}}}`
encrypted := buildEncryptedTobeparsedPayload(t, []byte(`{"sourceUrls":[{"sourceUrl":"https://stream.test/video.mp4","sourceName":"default"}]}`))
provider := &AllAnimeProvider{
httpClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusOK, searchBody), nil
}),
},
utlsClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusOK, `{"data":{"tobeparsed":"`+encrypted+`"}}`), nil
}),
},
extractor: newProviderExtractor(),
}
result, err := provider.GetStreams(context.Background(), 1, []string{"Test Anime"}, "1", "sub")
if err != nil {
t.Fatalf("GetStreams: %v", err)
}
if result.URL != "https://stream.test/video.mp4" {
t.Errorf("URL = %q", result.URL)
}
if result.Referer != allAnimeReferer {
t.Errorf("Referer = %q", result.Referer)
}
if result.Type != "mp4" {
t.Errorf("Type = %q", result.Type)
}
}
func TestGetStreams_ShowNotFound(t *testing.T) {
t.Parallel()
provider := &AllAnimeProvider{
httpClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusOK, `{"data":{"shows":{"edges":[]}}}`), nil
}),
},
utlsClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
t.Error("should not call episode sources when show not found")
return nil, nil
}),
},
extractor: newProviderExtractor(),
}
_, err := provider.GetStreams(context.Background(), 999, []string{"Nothing"}, "1", "sub")
if err == nil {
t.Fatal("expected error for show not found")
}
}
func TestGetStreams_NoSources(t *testing.T) {
t.Parallel()
provider := &AllAnimeProvider{
httpClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusOK, `{"data":{"shows":{"edges":[{"_id":"showX","malId":"1","name":"Anime"}]}}}`), nil
}),
},
utlsClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusNotFound, `not found`), nil
}),
},
extractor: newProviderExtractor(),
}
_, err := provider.GetStreams(context.Background(), 1, []string{"Anime"}, "1", "sub")
if err == nil {
t.Fatal("expected error when no sources")
}
}
func TestParseProviderResponse(t *testing.T) {
t.Parallel()
t.Run("extracts links and subtitles", func(t *testing.T) {
t.Parallel()
body := `{"links":[{"link":"https://cdn.test/video.mp4","resolutionStr":"1080p"}],"subtitles":[{"lang":"en","src":"https://sub.test/en.vtt"}]}`
extractor := &providerExtractor{
baseURL: allAnimeSiteURL,
referer: allAnimeReferer,
}
sources := extractor.parseProviderResponse(context.Background(), body)
if len(sources) == 0 {
t.Fatal("expected at least one source")
}
if sources[0].URL != "https://cdn.test/video.mp4" {
t.Errorf("URL = %q", sources[0].URL)
}
if sources[0].Quality != "1080p" {
t.Errorf("Quality = %q", sources[0].Quality)
}
if len(sources[0].Subtitles) != 1 {
t.Fatalf("subtitles count = %d, want 1", len(sources[0].Subtitles))
}
if sources[0].Subtitles[0].Lang != "en" {
t.Errorf("sub lang = %q", sources[0].Subtitles[0].Lang)
}
})
t.Run("invalid json returns empty", func(t *testing.T) {
t.Parallel()
extractor := &providerExtractor{
baseURL: allAnimeSiteURL,
referer: allAnimeReferer,
}
sources := extractor.parseProviderResponse(context.Background(), "not json")
if len(sources) != 0 {
t.Errorf("expected empty, got %d sources", len(sources))
}
})
t.Run("empty response returns empty", func(t *testing.T) {
t.Parallel()
extractor := &providerExtractor{
baseURL: allAnimeSiteURL,
referer: allAnimeReferer,
}
sources := extractor.parseProviderResponse(context.Background(), "{}")
if len(sources) != 0 {
t.Errorf("expected empty, got %d sources", len(sources))
}
})
}
func TestParseExternalEmbedResponse(t *testing.T) {
t.Parallel()
t.Run("ok.ru extracts hls manifest", func(t *testing.T) {
t.Parallel()
body := `{"flashvars":{"metadata":"{\"hlsManifestUrl\":\"https://ok.example.test/playlist.m3u8\"}"}}`
sources := parseExternalEmbedResponse("https://ok.ru/video/123", body, allAnimeReferer)
if len(sources) != 1 {
t.Fatalf("got %d sources, want 1", len(sources))
}
if sources[0].URL != "https://ok.example.test/playlist.m3u8" {
t.Errorf("URL = %q", sources[0].URL)
}
if sources[0].Provider != "ok" {
t.Errorf("Provider = %q", sources[0].Provider)
}
})
t.Run("mp4upload extracts src", func(t *testing.T) {
t.Parallel()
body := `src: "https://mp4upload.example.test/video.mp4"`
sources := parseExternalEmbedResponse("https://mp4upload.com/e/abc", body, allAnimeReferer)
if len(sources) != 1 {
t.Fatalf("got %d sources, want 1", len(sources))
}
if sources[0].URL != "https://mp4upload.example.test/video.mp4" {
t.Errorf("URL = %q", sources[0].URL)
}
if sources[0].Provider != "mp4upload" {
t.Errorf("Provider = %q", sources[0].Provider)
}
})
t.Run("unknown embed returns empty", func(t *testing.T) {
t.Parallel()
sources := parseExternalEmbedResponse("https://unknown.example.com/video", "<html></html>", allAnimeReferer)
if len(sources) != 0 {
t.Errorf("expected empty, got %d sources", len(sources))
}
})
}
func TestParseM3U8Sources(t *testing.T) {
t.Parallel()
t.Run("parses bandwidth entries", func(t *testing.T) {
t.Parallel()
body := "#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=8000000,RESOLUTION=1920x1080\n1080p.m3u8\n#EXT-X-STREAM-INF:BANDWIDTH=5000000\n720p.m3u8\n#EXT-X-STREAM-INF:BANDWIDTH=2500000\n480p.m3u8"
masterURL := "https://cdn.test/master.m3u8"
sources := parseM3U8Sources(body, masterURL, allAnimeReferer)
if len(sources) != 3 {
t.Fatalf("got %d sources, want 3", len(sources))
}
expected := []struct {
url string
quality string
}{
{"https://cdn.test/1080p.m3u8", "1080p"},
{"https://cdn.test/720p.m3u8", "720p"},
{"https://cdn.test/480p.m3u8", "480p"},
}
for i, exp := range expected {
if sources[i].URL != exp.url {
t.Errorf("sources[%d].URL = %q, want %q", i, sources[i].URL, exp.url)
}
if sources[i].Quality != exp.quality {
t.Errorf("sources[%d].Quality = %q, want %q", i, sources[i].Quality, exp.quality)
}
if sources[i].Type != "m3u8" {
t.Errorf("sources[%d].Type = %q", i, sources[i].Type)
}
}
})
t.Run("empty body returns nothing", func(t *testing.T) {
t.Parallel()
sources := parseM3U8Sources("", "https://cdn.test/master.m3u8", allAnimeReferer)
if len(sources) != 0 {
t.Errorf("expected empty, got %d", len(sources))
}
})
t.Run("absolute URLs not rebased", func(t *testing.T) {
t.Parallel()
body := "#EXT-X-STREAM-INF:BANDWIDTH=8000000\nhttps://cdn2.test/video.m3u8"
sources := parseM3U8Sources(body, "https://cdn.test/master.m3u8", allAnimeReferer)
if len(sources) != 1 {
t.Fatalf("got %d sources", len(sources))
}
if sources[0].URL != "https://cdn2.test/video.m3u8" {
t.Errorf("URL = %q", sources[0].URL)
}
})
}
func TestExtractVideoLinks(t *testing.T) {
t.Parallel()
t.Run("fetches and parses provider response", func(t *testing.T) {
t.Parallel()
extractor := &providerExtractor{
httpClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
if req.Method != http.MethodGet {
t.Errorf("method = %q, want GET", req.Method)
}
if req.Header.Get("Referer") != allAnimeReferer {
t.Errorf("Referer = %q", req.Header.Get("Referer"))
}
body := `{"links":[{"link":"https://cdn.test/video.mp4","resolutionStr":"720p"}]}`
return mockStringResponse(http.StatusOK, body), nil
}),
},
baseURL: allAnimeSiteURL,
referer: allAnimeReferer,
}
sources, err := extractor.ExtractVideoLinks(context.Background(), "/some-path")
if err != nil {
t.Fatalf("ExtractVideoLinks: %v", err)
}
if len(sources) != 1 {
t.Fatalf("got %d sources, want 1", len(sources))
}
if sources[0].Provider != "wixmp" {
t.Errorf("Provider = %q", sources[0].Provider)
}
})
t.Run("server error returns empty sources", func(t *testing.T) {
t.Parallel()
extractor := &providerExtractor{
httpClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusInternalServerError, ""), nil
}),
},
baseURL: allAnimeSiteURL,
referer: allAnimeReferer,
}
sources, err := extractor.ExtractVideoLinks(context.Background(), "/error-path")
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if len(sources) != 0 {
t.Errorf("expected empty sources, got %d", len(sources))
}
})
}
func TestExtractEmbedVideoLinks(t *testing.T) {
t.Parallel()
t.Run("ok.ru embed extracted", func(t *testing.T) {
t.Parallel()
extractor := &providerExtractor{
httpClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
body := `{"flashvars":{"metadata":"{\"hlsManifestUrl\":\"https://ok.test/play.m3u8\"}"}}`
return mockStringResponse(http.StatusOK, body), nil
}),
},
referer: allAnimeReferer,
}
sources, err := extractor.ExtractEmbedVideoLinks(context.Background(), "https://ok.ru/video/123")
if err != nil {
t.Fatalf("ExtractEmbedVideoLinks: %v", err)
}
if len(sources) != 1 {
t.Fatalf("got %d sources, want 1", len(sources))
}
if sources[0].URL != "https://ok.test/play.m3u8" {
t.Errorf("URL = %q", sources[0].URL)
}
})
t.Run("unknown embed returns empty", func(t *testing.T) {
t.Parallel()
extractor := &providerExtractor{
httpClient: &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
return mockStringResponse(http.StatusOK, "<html></html>"), nil
}),
},
referer: allAnimeReferer,
}
sources, err := extractor.ExtractEmbedVideoLinks(context.Background(), "https://unknown.com/video")
if err != nil {
t.Fatalf("ExtractEmbedVideoLinks: %v", err)
}
if len(sources) != 0 {
t.Errorf("expected empty, got %d sources", len(sources))
}
})
}

View File

@@ -0,0 +1,23 @@
package allanime
import (
"io"
"net/http"
"strings"
)
type roundTripFunc func(*http.Request) (*http.Response, error)
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req)
}
func mockStringResponse(status int, body string) *http.Response {
hdr := make(http.Header)
hdr.Set("Content-Type", "application/json")
return &http.Response{
StatusCode: status,
Header: hdr,
Body: io.NopCloser(strings.NewReader(body)),
}
}