diff --git a/cmd/archivistactl/cmd/store.go b/cmd/archivistactl/cmd/store.go index 87b3e59d..701217bd 100644 --- a/cmd/archivistactl/cmd/store.go +++ b/cmd/archivistactl/cmd/store.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Archivista Contributors +// Copyright 2022-2024 The Archivista Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -54,7 +54,7 @@ func storeAttestationByPath(ctx context.Context, baseUrl, path string) (string, } defer file.Close() - resp, err := api.UploadWithReader(ctx, baseUrl, file) + resp, err := api.StoreWithReader(ctx, baseUrl, file) if err != nil { return "", err } diff --git a/pkg/api/download.go b/pkg/api/download.go index 06c67883..df76ae69 100644 --- a/pkg/api/download.go +++ b/pkg/api/download.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Witness Contributors +// Copyright 2023-2024 The Witness Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -26,9 +26,40 @@ import ( "github.com/in-toto/go-witness/dsse" ) -func Download(ctx context.Context, baseUrl string, gitoid string) (dsse.Envelope, error) { +func DownloadReadCloser(ctx context.Context, baseURL string, gitoid string) (io.ReadCloser, error) { + return DownloadReadCloserWithHTTPClient(ctx, &http.Client{}, baseURL, gitoid) +} + +func DownloadReadCloserWithHTTPClient(ctx context.Context, client *http.Client, baseURL string, gitoid string) (io.ReadCloser, error) { + downloadURL, err := url.JoinPath(baseURL, "download", gitoid) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, nil) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + // NOTE: attempt to read body on error and + // only close if an error occurs + defer resp.Body.Close() + errMsg, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return nil, errors.New(string(errMsg)) + } + return resp.Body, nil +} + +func Download(ctx context.Context, baseURL string, gitoid string) (dsse.Envelope, error) { buf := &bytes.Buffer{} - if err := DownloadWithWriter(ctx, baseUrl, gitoid, buf); err != nil { + if err := DownloadWithWriter(ctx, baseURL, gitoid, buf); err != nil { return dsse.Envelope{}, err } @@ -41,13 +72,17 @@ func Download(ctx context.Context, baseUrl string, gitoid string) (dsse.Envelope return env, nil } -func DownloadWithWriter(ctx context.Context, baseUrl, gitoid string, dst io.Writer) error { - downloadUrl, err := url.JoinPath(baseUrl, "download", gitoid) +func DownloadWithWriter(ctx context.Context, baseURL string, gitoid string, dst io.Writer) error { + return DownloadWithWriterWithHTTPClient(ctx, &http.Client{}, baseURL, gitoid, dst) +} + +func DownloadWithWriterWithHTTPClient(ctx context.Context, client *http.Client, baseURL string, gitoid string, dst io.Writer) error { + downloadUrl, err := url.JoinPath(baseURL, "download", gitoid) if err != nil { return err } - req, err := http.NewRequestWithContext(ctx, "GET", downloadUrl, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadUrl, nil) if err != nil { return err } diff --git a/pkg/api/graphql.go b/pkg/api/graphql.go index 73dbfb4e..a7f29d23 100644 --- a/pkg/api/graphql.go +++ b/pkg/api/graphql.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Witness Contributors +// Copyright 2023-2024 The Witness Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -25,28 +25,69 @@ import ( "net/url" ) -type graphQLError struct { - Message string `json:"message"` -} +const RetrieveSubjectsQuery = `query($gitoid: String!) { + subjects( + where: { + hasStatementWith:{ + hasDsseWith:{ + gitoidSha256: $gitoid + } + } + } + ) { + edges { + node{ + name + subjectDigests{ + algorithm + value + } + } + } + } +}` -type graphQLResponse[T any] struct { - Data T `json:"data,omitempty"` - Errors []graphQLError `json:"errors,omitempty"` -} +const SearchQuery = `query($algo: String!, $digest: String!) { + dsses( + where: { + hasStatementWith: { + hasSubjectsWith: { + hasSubjectDigestsWith: { + value: $digest, + algorithm: $algo + } + } + } + } + ) { + edges { + node { + gitoidSha256 + statement { + attestationCollections { + name + attestations { + type + } + } + } + } + } + } +}` -type graphQLRequestBody[TVars any] struct { - Query string `json:"query"` - Variables TVars `json:"variables,omitempty"` +func GraphQlQuery[TRes any, TVars any](ctx context.Context, baseUrl, query string, vars TVars) (TRes, error) { + return GraphQlQueryWithHeaders[TRes, TVars](ctx, baseUrl, query, vars, nil) } -func GraphQlQuery[TRes any, TVars any](ctx context.Context, baseUrl, query string, vars TVars) (TRes, error) { +func GraphQlQueryWithHeaders[TRes any, TVars any](ctx context.Context, baseUrl, query string, vars TVars, headers map[string]string) (TRes, error) { var response TRes queryUrl, err := url.JoinPath(baseUrl, "query") if err != nil { return response, err } - requestBody := graphQLRequestBody[TVars]{ + requestBody := GraphQLRequestBodyGeneric[TVars]{ Query: query, Variables: vars, } @@ -56,11 +97,15 @@ func GraphQlQuery[TRes any, TVars any](ctx context.Context, baseUrl, query strin return response, err } - req, err := http.NewRequestWithContext(ctx, "POST", queryUrl, bytes.NewReader(reqBody)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, queryUrl, bytes.NewReader(reqBody)) if err != nil { return response, err } + for k, v := range headers { + req.Header.Set(k, v) + } + req.Header.Set("Content-Type", "application/json") hc := &http.Client{} res, err := hc.Do(req) @@ -79,7 +124,7 @@ func GraphQlQuery[TRes any, TVars any](ctx context.Context, baseUrl, query strin } dec := json.NewDecoder(res.Body) - gqlRes := graphQLResponse[TRes]{} + gqlRes := GraphQLResponseGeneric[TRes]{} if err := dec.Decode(&gqlRes); err != nil { return response, err } diff --git a/pkg/api/structs.go b/pkg/api/structs.go new file mode 100644 index 00000000..fcf295a4 --- /dev/null +++ b/pkg/api/structs.go @@ -0,0 +1,90 @@ +// Copyright 2024 The Archivista Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +type GraphQLError struct { + Message string `json:"message"` +} + +type GraphQLResponseGeneric[T any] struct { + Data T `json:"data,omitempty"` + Errors []GraphQLError `json:"errors,omitempty"` +} + +type GraphQLRequestBodyGeneric[TVars any] struct { + Query string `json:"query"` + Variables TVars `json:"variables,omitempty"` +} + +type RetrieveSubjectVars struct { + Gitoid string `json:"gitoid"` +} + +type SearchVars struct { + Algorithm string `json:"algo"` + Digest string `json:"digest"` +} + +type RetrieveSubjectResults struct { + Subjects Subjects `json:"subjects"` +} + +type Subjects struct { + Edges []SubjectEdge `json:"edges"` +} + +type SubjectEdge struct { + Node SubjectNode `json:"node"` +} + +type SubjectNode struct { + Name string `json:"name"` + SubjectDigests []SubjectDigest `json:"subjectDigests"` +} + +type SubjectDigest struct { + Algorithm string `json:"algorithm"` + Value string `json:"value"` +} + +type SearchResults struct { + Dsses DSSES `json:"dsses"` +} + +type DSSES struct { + Edges []SearchEdge `json:"edges"` +} + +type SearchEdge struct { + Node SearchNode `json:"node"` +} + +type SearchNode struct { + GitoidSha256 string `json:"gitoidSha256"` + Statement Statement `json:"statement"` +} + +type Statement struct { + AttestationCollection AttestationCollection `json:"attestationCollections"` +} + +type AttestationCollection struct { + Name string `json:"name"` + Attestations []Attestation `json:"attestations"` +} + +type Attestation struct { + Type string `json:"type"` +} diff --git a/pkg/api/upload.go b/pkg/api/upload.go index 94504412..3bf99344 100644 --- a/pkg/api/upload.go +++ b/pkg/api/upload.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Witness Contributors +// Copyright 2023-2024 The Archivista Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -33,28 +33,27 @@ type UploadResponse struct { // Deprecated: Use UploadResponse instead. It will be removed in version >= v0.6.0 type StoreResponse = UploadResponse -// Deprecated: Use Upload instead. It will be removed in version >= v0.6.0 -func Store(ctx context.Context, baseUrl string, envelope dsse.Envelope) (StoreResponse, error) { - return Upload(ctx, baseUrl, envelope) +// Deprecated: Use Store instead. It will be removed in version >= v0.6.0 +func Upload(ctx context.Context, baseURL string, envelope dsse.Envelope) (UploadResponse, error) { + return Store(ctx, baseURL, envelope) } -func Upload(ctx context.Context, baseUrl string, envelope dsse.Envelope) (StoreResponse, error) { +func Store(ctx context.Context, baseURL string, envelope dsse.Envelope) (StoreResponse, error) { buf := &bytes.Buffer{} enc := json.NewEncoder(buf) if err := enc.Encode(envelope); err != nil { return StoreResponse{}, err } - return UploadWithReader(ctx, baseUrl, buf) + return StoreWithReader(ctx, baseURL, buf) } -// Deprecated: Use UploadWithReader instead. It will be removed in version >= v0.6.0 -func StoreWithReader(ctx context.Context, baseUrl string, r io.Reader) (StoreResponse, error) { - return UploadWithReader(ctx, baseUrl, r) +func StoreWithReader(ctx context.Context, baseURL string, r io.Reader) (StoreResponse, error) { + return StoreWithReaderWithHTTPClient(ctx, &http.Client{}, baseURL, r) } -func UploadWithReader(ctx context.Context, baseUrl string, r io.Reader) (StoreResponse, error) { - uploadPath, err := url.JoinPath(baseUrl, "upload") +func StoreWithReaderWithHTTPClient(ctx context.Context, client *http.Client, baseURL string, r io.Reader) (StoreResponse, error) { + uploadPath, err := url.JoinPath(baseURL, "upload") if err != nil { return UploadResponse{}, err } diff --git a/pkg/http-client/client.go b/pkg/http-client/client.go new file mode 100644 index 00000000..22b87c1a --- /dev/null +++ b/pkg/http-client/client.go @@ -0,0 +1,216 @@ +// Copyright 2024 The Archivista Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package httpclient + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + + "github.com/in-toto/archivista/pkg/api" + "github.com/in-toto/go-witness/dsse" +) + +type ArchivistaClient struct { + BaseURL string + GraphQLURL string + *http.Client +} + +type HttpClienter interface { + DownloadDSSE(ctx context.Context, gitoid string) (dsse.Envelope, error) + DownloadReadCloser(ctx context.Context, gitoid string) (io.ReadCloser, error) + DownloadWithWriter(ctx context.Context, gitoid string, dst io.Writer) error + Store(ctx context.Context, envelope dsse.Envelope) (api.UploadResponse, error) + StoreWithReader(ctx context.Context, r io.Reader) (api.UploadResponse, error) + GraphQLRetrieveSubjectResults(ctx context.Context, gitoid string) (api.RetrieveSubjectResults, error) + GraphQLRetrieveSearchResults(ctx context.Context, algo string, digest string) (api.SearchResults, error) + GraphQLQueryIface(ctx context.Context, query string, variables interface{}) (*GraphQLResponseInterface, error) + GraphQLQueryToDst(ctx context.Context, query string, variables interface{}, dst interface{}) error + GraphQLQueryReadCloser(ctx context.Context, query string, variables interface{}) (io.ReadCloser, error) +} + +func CreateArchivistaClient(httpClient *http.Client, baseURL string) (*ArchivistaClient, error) { + client := ArchivistaClient{ + BaseURL: baseURL, + Client: http.DefaultClient, + } + if httpClient != nil { + client.Client = httpClient + } + var err error + client.GraphQLURL, err = url.JoinPath(client.BaseURL, "query") + if err != nil { + return nil, err + } + return &client, nil +} + +func (ac *ArchivistaClient) DownloadDSSE(ctx context.Context, gitoid string) (dsse.Envelope, error) { + reader, err := api.DownloadReadCloserWithHTTPClient(ctx, ac.Client, ac.BaseURL, gitoid) + if err != nil { + return dsse.Envelope{}, err + } + env := dsse.Envelope{} + if err := json.NewDecoder(reader).Decode(&env); err != nil { + return dsse.Envelope{}, err + } + return env, nil +} + +func (ac *ArchivistaClient) DownloadReadCloser(ctx context.Context, gitoid string) (io.ReadCloser, error) { + return api.DownloadReadCloserWithHTTPClient(ctx, ac.Client, ac.BaseURL, gitoid) +} + +func (ac *ArchivistaClient) DownloadWithWriter(ctx context.Context, gitoid string, dst io.Writer) error { + return api.DownloadWithWriterWithHTTPClient(ctx, ac.Client, ac.BaseURL, gitoid, dst) +} + +func (ac *ArchivistaClient) Store(ctx context.Context, envelope dsse.Envelope) (api.UploadResponse, error) { + return api.Store(ctx, ac.BaseURL, envelope) +} + +func (ac *ArchivistaClient) StoreWithReader(ctx context.Context, r io.Reader) (api.UploadResponse, error) { + return api.StoreWithReader(ctx, ac.BaseURL, r) +} + +type GraphQLRequestBodyInterface struct { + Query string `json:"query"` + Variables interface{} `json:"variables,omitempty"` +} + +type GraphQLResponseInterface struct { + Data interface{} + Errors []api.GraphQLError `json:"errors,omitempty"` +} + +// GraphQLRetrieveSubjectResults retrieves the subjects for a given gitoid. +func (ac *ArchivistaClient) GraphQLRetrieveSubjectResults( + ctx context.Context, + gitoid string, +) (api.RetrieveSubjectResults, error) { + return api.GraphQlQuery[api.RetrieveSubjectResults]( + ctx, + ac.BaseURL, + api.RetrieveSubjectsQuery, + api.RetrieveSubjectVars{Gitoid: gitoid}, + ) +} + +// GraphQLRetrieveSearchResults retrieves the search results for a given algorithm and digest. +func (ac *ArchivistaClient) GraphQLRetrieveSearchResults( + ctx context.Context, + algo string, + digest string, +) (api.SearchResults, error) { + return api.GraphQlQuery[api.SearchResults]( + ctx, + ac.BaseURL, + api.SearchQuery, + api.SearchVars{Algorithm: algo, Digest: digest}, + ) +} + +// GraphQLQueryIface executes a GraphQL query against the Archivista API and returns the response as an interface. +// +// Parameters: +// - ctx: The context to control the query's lifecycle, such as cancellations or deadlines. +// - query: A string representing the GraphQL query to be executed. +// - variables: A map or struct containing variables to parameterize the query. +// +// Returns: +// - A pointer to a GraphQLResponseInterface containing the query's result or errors. +// - An error if the query execution or response parsing fails. +// +// Example: +// +// response, err := client.GraphQLQueryIface(ctx, query, variables) +// if err != nil { +// log.Fatalf("GraphQL query failed: %v", err) +// } +// fmt.Printf("Response data: %+v\n", response.Data) +func (ac *ArchivistaClient) GraphQLQueryIface( + ctx context.Context, + query string, + variables interface{}, +) (*GraphQLResponseInterface, error) { + reader, err := ac.GraphQLQueryReadCloser(ctx, query, variables) + if err != nil { + return nil, err + } + defer reader.Close() + gqlRes := GraphQLResponseInterface{} + dec := json.NewDecoder(reader) + if err := dec.Decode(&gqlRes); err != nil { + return nil, err + } + if len(gqlRes.Errors) > 0 { + return nil, fmt.Errorf("graph ql query failed: %v", gqlRes.Errors) + } + return &gqlRes, nil +} + +// GraphQLQueryToDst executes a GraphQL query against the Archivista API and unmarshals the response into a destination object. +func (ac *ArchivistaClient) GraphQLQueryToDst(ctx context.Context, query string, variables interface{}, dst interface{}) error { + reader, err := ac.GraphQLQueryReadCloser(ctx, query, variables) + if err != nil { + return err + } + defer reader.Close() + dec := json.NewDecoder(reader) + if err := dec.Decode(&dst); err != nil { + return err + } + return nil +} + +// GraphQLQueryReadCloser executes a GraphQL query against the Archivista API and returns the response as an io.ReadCloser. +func (ac *ArchivistaClient) GraphQLQueryReadCloser( + ctx context.Context, + query string, + variables interface{}, +) (io.ReadCloser, error) { + requestBodyMap := GraphQLRequestBodyInterface{ + Query: query, + Variables: variables, + } + requestBodyJSON, err := json.Marshal(requestBodyMap) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, ac.GraphQLURL, bytes.NewReader(requestBodyJSON)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + res, err := ac.Do(req) + if err != nil { + return nil, err + } + if res.StatusCode != http.StatusOK { + defer res.Body.Close() + errMsg, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + return nil, errors.New(string(errMsg)) + } + return res.Body, nil +} diff --git a/pkg/http-client/client_test.go b/pkg/http-client/client_test.go new file mode 100644 index 00000000..598f64ad --- /dev/null +++ b/pkg/http-client/client_test.go @@ -0,0 +1,320 @@ +// Copyright 2024 The Archivista Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package httpclient_test + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/in-toto/archivista/pkg/api" + httpclient "github.com/in-toto/archivista/pkg/http-client" + "github.com/in-toto/go-witness/dsse" + "github.com/stretchr/testify/suite" +) + +// Test Suite: UT HTTPClientDownloadSuite +type UTHTTPClientDownloadSuite struct { + suite.Suite +} + +func TestHTTPClientAPIDownloadSuite(t *testing.T) { + suite.Run(t, new(UTHTTPClientDownloadSuite)) +} + +func (ut *UTHTTPClientDownloadSuite) Test_DownloadDSSE() { + testEnvelope, err := os.ReadFile("../../test/package.attestation.json") + if err != nil { + ut.FailNow(err.Error()) + } + expectedEnvelop := dsse.Envelope{} + err = json.Unmarshal(testEnvelope, &expectedEnvelop) + if err != nil { + ut.FailNow(err.Error()) + } + testServer := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err = w.Write(testEnvelope) + if err != nil { + ut.FailNow(err.Error()) + } + }, + ), + ) + defer testServer.Close() + ctx := context.TODO() + client, err := httpclient.CreateArchivistaClient(http.DefaultClient, testServer.URL) + if err != nil { + ut.FailNow(err.Error()) + } + resp, err := client.DownloadDSSE(ctx, "gitoid_test") + if err != nil { + ut.FailNow(err.Error()) + } + ut.Equal(expectedEnvelop, resp) +} + +func (ut *UTHTTPClientDownloadSuite) Test_DownloadReadCloser() { + testEnvelope, err := os.ReadFile("../../test/package.attestation.json") + if err != nil { + ut.FailNow(err.Error()) + } + expectedEnvelop := dsse.Envelope{} + err = json.Unmarshal(testEnvelope, &expectedEnvelop) + if err != nil { + ut.FailNow(err.Error()) + } + testServer := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err = w.Write(testEnvelope) + if err != nil { + ut.FailNow(err.Error()) + } + }, + ), + ) + defer testServer.Close() + ctx := context.TODO() + client, err := httpclient.CreateArchivistaClient(http.DefaultClient, testServer.URL) + if err != nil { + ut.FailNow(err.Error()) + } + readCloser, err := client.DownloadReadCloser(ctx, "gitoid_test") + if err != nil { + ut.FailNow(err.Error()) + } + env := dsse.Envelope{} + if err := json.NewDecoder(readCloser).Decode(&env); err != nil { + ut.FailNow(err.Error()) + } + ut.Equal(expectedEnvelop, env) +} + +func (ut *UTHTTPClientDownloadSuite) Test_DownloadWithWriter() { + testEnvelope, err := os.ReadFile("../../test/package.attestation.json") + if err != nil { + ut.FailNow(err.Error()) + } + expectedEnvelop := dsse.Envelope{} + err = json.Unmarshal(testEnvelope, &expectedEnvelop) + if err != nil { + ut.FailNow(err.Error()) + } + testServer := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err = w.Write(testEnvelope) + if err != nil { + ut.FailNow(err.Error()) + } + }, + ), + ) + defer testServer.Close() + ctx := context.TODO() + client, err := httpclient.CreateArchivistaClient(http.DefaultClient, testServer.URL) + if err != nil { + ut.FailNow(err.Error()) + } + buf := bytes.NewBuffer(nil) + if err := client.DownloadWithWriter(ctx, "gitoid_test", buf); err != nil { + ut.FailNow(err.Error()) + } + env := dsse.Envelope{} + if err := json.NewDecoder(buf).Decode(&env); err != nil { + ut.FailNow(err.Error()) + } + ut.Equal(expectedEnvelop, env) +} + +// Test Suite: UT HTTPClientStore +type UTHTTPClientStoreSuite struct { + suite.Suite +} + +func TestAPIStoreSuite(t *testing.T) { + suite.Run(t, new(UTHTTPClientStoreSuite)) +} + +func (ut *UTHTTPClientStoreSuite) Test_Store() { + testServer := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(`{"gitoid":"test"}`)) + if err != nil { + ut.FailNow(err.Error()) + } + }, + ), + ) + defer testServer.Close() + ctx := context.TODO() + attFile, err := os.ReadFile("../../test/package.attestation.json") + if err != nil { + ut.FailNow(err.Error()) + } + attEnvelop := dsse.Envelope{} + err = json.Unmarshal(attFile, &attEnvelop) + if err != nil { + ut.FailNow(err.Error()) + } + client, err := httpclient.CreateArchivistaClient(http.DefaultClient, testServer.URL) + if err != nil { + ut.FailNow(err.Error()) + } + resp, err := client.Store(ctx, attEnvelop) + if err != nil { + ut.FailNow(err.Error()) + } + ut.Equal(resp, api.UploadResponse{Gitoid: "test"}) +} + +func (ut *UTHTTPClientStoreSuite) Test_StoreWithReader() { + testServer := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(`{"gitoid":"test"}`)) + if err != nil { + ut.FailNow(err.Error()) + } + }, + ), + ) + defer testServer.Close() + attIo, err := os.Open("../../test/package.attestation.json") + if err != nil { + ut.FailNow(err.Error()) + } + ctx := context.TODO() + client, err := httpclient.CreateArchivistaClient(http.DefaultClient, testServer.URL) + if err != nil { + ut.FailNow(err.Error()) + } + resp, err := client.StoreWithReader(ctx, attIo) + if err != nil { + ut.FailNow(err.Error()) + } + ut.Equal(resp, api.UploadResponse{Gitoid: "test"}) +} + +// Test Suite: UT HTTPClientStore +type UTHTTPClientGraphQLSuite struct { + suite.Suite +} + +func TestAPIGraphQLSuite(t *testing.T) { + suite.Run(t, new(UTHTTPClientGraphQLSuite)) +} + +func (ut *UTHTTPClientGraphQLSuite) Test_GraphQLRetrieveSubjectResults() { + expected := api.GraphQLResponseGeneric[api.RetrieveSubjectResults]{ + Data: api.RetrieveSubjectResults{ + Subjects: api.Subjects{ + Edges: []api.SubjectEdge{ + { + Node: api.SubjectNode{ + Name: "test_Gitoid", + SubjectDigests: []api.SubjectDigest{ + { + Algorithm: "test_Gitoid", + Value: "test_Gitoid", + }, + }, + }, + }, + }, + }, + }, + Errors: []api.GraphQLError{}, + } + testServer := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(expected); err != nil { + ut.FailNow(err.Error()) + } + }, + ), + ) + defer testServer.Close() + ctx := context.TODO() + client, err := httpclient.CreateArchivistaClient(http.DefaultClient, testServer.URL) + if err != nil { + ut.FailNow(err.Error()) + } + actual, err := client.GraphQLRetrieveSubjectResults(ctx, "test_Gitoid") + ut.NoError(err) + ut.Equal(expected.Data, actual) +} + +func (ut *UTHTTPClientGraphQLSuite) Test_GraphQLSearchResults() { + expected := api.GraphQLResponseGeneric[api.SearchResults]{ + Data: api.SearchResults{ + Dsses: api.DSSES{ + Edges: []api.SearchEdge{ + { + Node: api.SearchNode{ + GitoidSha256: "test_Gitoid", + Statement: api.Statement{ + AttestationCollection: api.AttestationCollection{ + Name: "test_Gitoid", + Attestations: []api.Attestation{ + { + Type: "test", + }, + }, + }, + }, + }, + }, + }, + }, + }, + Errors: []api.GraphQLError{}, + } + testServer := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(expected); err != nil { + ut.FailNow(err.Error()) + } + }, + ), + ) + defer testServer.Close() + ctx := context.TODO() + client, err := httpclient.CreateArchivistaClient(http.DefaultClient, testServer.URL) + if err != nil { + ut.FailNow(err.Error()) + } + actual, err := client.GraphQLRetrieveSearchResults(ctx, "test_Gitoid", "test_Gitoid") + ut.NoError(err) + ut.Equal(expected.Data, actual) +}