diff --git a/internal/testing/testdata/models.go b/internal/testing/testdata/models.go index a485bd10b7..f018a2135c 100644 --- a/internal/testing/testdata/models.go +++ b/internal/testing/testdata/models.go @@ -23,6 +23,8 @@ import ( ) var T1, _ = time.Parse(time.RFC3339, "2023-01-01T00:00:00Z") +var T2 = time.Unix(1e9, 0) +var T3 = time.Unix(1e9+5, 0) var A1 = &model.ArtifactInputSpec{ Algorithm: "sha256", @@ -399,3 +401,45 @@ var NoVulnInput = &model.VulnerabilityInputSpec{ var NoVulnOut = &model.VulnerabilityID{ VulnerabilityID: "", } + +var L1 = &model.LicenseInputSpec{ + Name: "BSD-3-Clause", + ListVersion: ptrfrom.String("3.21 2023-06-18"), +} +var L1out = &model.License{ + Name: "BSD-3-Clause", + ListVersion: ptrfrom.String("3.21 2023-06-18"), +} +var L2 = &model.LicenseInputSpec{ + Name: "GPL-2.0-or-later", + ListVersion: ptrfrom.String("3.21 2023-06-18"), +} +var L2out = &model.License{ + Name: "GPL-2.0-or-later", + ListVersion: ptrfrom.String("3.21 2023-06-18"), +} +var L3 = &model.LicenseInputSpec{ + Name: "MPL-2.0", + ListVersion: ptrfrom.String("1.23 2020"), +} +var L3out = &model.License{ + Name: "MPL-2.0", + ListVersion: ptrfrom.String("1.23 2020"), +} + +var InlineLicense = ` +Redistribution and use of the MAME code or any derivative works are permitted provided that the following conditions are met: +* Redistributions may not be sold, nor may they be used in a commercial product or activity. +* Redistributions that are modified from the original source must include the complete source code, including the source code for all components used by a binary built from the modified sources. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. +* Redistributions must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +` + +var L4 = &model.LicenseInputSpec{ + Name: "LicenseRef-d58b4101", + Inline: &InlineLicense, +} +var L4out = &model.License{ + Name: "LicenseRef-d58b4101", + Inline: &InlineLicense, +} diff --git a/pkg/assembler/backends/arangodb/backend.go b/pkg/assembler/backends/arangodb/backend.go index d5dc320f9f..a2d11ccae6 100644 --- a/pkg/assembler/backends/arangodb/backend.go +++ b/pkg/assembler/backends/arangodb/backend.go @@ -27,7 +27,6 @@ import ( arangodbdriverhttp "github.com/arangodb/go-driver/http" "github.com/guacsec/guac/internal/testing/ptrfrom" "github.com/guacsec/guac/pkg/assembler/backends" - "github.com/guacsec/guac/pkg/assembler/graphql/model" ) const ( @@ -72,6 +71,10 @@ const ( vulnHasVulnerabilityIDStr string = "vulnHasVulnerabilityID" vulnerabilitiesStr string = "vulnerabilities" + // license collection + + licensesStr string = "licenses" + // isDependency collections isDependencyDepPkgVersionEdgesStr string = "isDependencyDepPkgVersionEdges" @@ -178,6 +181,14 @@ const ( certifyGoodSrcEdgesStr string = "certifyGoodSrcEdges" certifyGoodArtEdgesStr string = "certifyGoodArtEdges" certifyGoodsStr string = "certifyGoods" + + // certifyLegal collection + + certifyLegalPkgEdgesStr string = "certifyLegalPkgEdges" + certifyLegalSrcEdgesStr string = "certifyLegalSrcEdges" + certifyLegalDeclaredLicensesEdgesStr string = "certifyLegalDeclaredLicensesEdges" + certifyLegalDiscoveredLicensesEdgesStr string = "certifyLegalDiscoveredLicensesEdges" + certifyLegalsStr string = "certifyLegals" ) type ArangoConfig struct { @@ -555,6 +566,27 @@ func getBackend(ctx context.Context, args backends.BackendArgs) (backends.Backen certifyGoodSrcEdges.From = []string{srcNamesStr} certifyGoodSrcEdges.To = []string{certifyGoodsStr} + // setup certifyLegal collections + var certifyLegalPkgEdges driver.EdgeDefinition + certifyLegalPkgEdges.Collection = certifyLegalPkgEdgesStr + certifyLegalPkgEdges.From = []string{pkgVersionsStr} + certifyLegalPkgEdges.To = []string{certifyLegalsStr} + + var certifyLegalSrcEdges driver.EdgeDefinition + certifyLegalSrcEdges.Collection = certifyLegalSrcEdgesStr + certifyLegalSrcEdges.From = []string{srcNamesStr} + certifyLegalSrcEdges.To = []string{certifyLegalsStr} + + var certifyLegalDeclaredLicensesEdges driver.EdgeDefinition + certifyLegalDeclaredLicensesEdges.Collection = certifyLegalDeclaredLicensesEdgesStr + certifyLegalDeclaredLicensesEdges.From = []string{certifyLegalsStr} + certifyLegalDeclaredLicensesEdges.To = []string{licensesStr} + + var certifyLegalDiscoveredLicensesEdges driver.EdgeDefinition + certifyLegalDiscoveredLicensesEdges.Collection = certifyLegalDiscoveredLicensesEdgesStr + certifyLegalDiscoveredLicensesEdges.From = []string{certifyLegalsStr} + certifyLegalDiscoveredLicensesEdges.To = []string{licensesStr} + // A graph can contain additional vertex collections, defined in the set of orphan collections var options driver.CreateGraphOptions options.EdgeDefinitions = []driver.EdgeDefinition{pkgHasNamespace, pkgHasName, @@ -566,7 +598,8 @@ func getBackend(ctx context.Context, args backends.BackendArgs) (backends.Backen certifyVexPkgEdges, certifyVexArtEdges, certifyVexVulnEdges, vulnMetadataEdges, vulnEqualVulnEdges, vulnEqualSubjectVulnEdges, pkgEqualPkgEdges, pkgEqualSubjectPkgEdges, hasMetadataPkgVersionEdges, hasMetadataPkgNameEdges, hasMetadataArtEdges, hasMetadataSrcEdges, pointOfContactPkgVersionEdges, pointOfContactPkgNameEdges, - pointOfContactArtEdges, pointOfContactSrcEdges, hasSourceAtEdges, hasSourceAtPkgVersionEdges, hasSourceAtPkgNameEdges} + pointOfContactArtEdges, pointOfContactSrcEdges, hasSourceAtEdges, hasSourceAtPkgVersionEdges, hasSourceAtPkgNameEdges, + certifyLegalPkgEdges, certifyLegalSrcEdges, certifyLegalDeclaredLicensesEdges, certifyLegalDiscoveredLicensesEdges} // create a graph graph, err = db.CreateGraphV2(ctx, "guac", &options) @@ -597,6 +630,10 @@ func getBackend(ctx context.Context, args backends.BackendArgs) (backends.Backen return nil, fmt.Errorf("failed to generate index for vulnerabilities: %w", err) } + if err := createIndexPerCollection(ctx, db, licensesStr, []string{"name", "inline", "listversion"}, true, "byNameInlineListVer"); err != nil { + return nil, fmt.Errorf("failed to generate index for licenses: %w", err) + } + if err := createIndexPerCollection(ctx, db, hashEqualsStr, []string{"artifactID", "equalArtifactID", "justification"}, true, "byArtIDEqualArtIDJust"); err != nil { return nil, fmt.Errorf("failed to generate index for hashEquals: %w", err) } @@ -912,25 +949,6 @@ func getPreloadString(prefix, name string) string { return name } -func (c *arangoClient) Licenses(ctx context.Context, licenseSpec *model.LicenseSpec) ([]*model.License, error) { - panic(fmt.Errorf("not implemented: Licenses")) -} -func (c *arangoClient) IngestLicense(ctx context.Context, license *model.LicenseInputSpec) (*model.License, error) { - panic(fmt.Errorf("not implemented: IngestLicense")) -} -func (c *arangoClient) IngestLicenses(ctx context.Context, licenses []*model.LicenseInputSpec) ([]*model.License, error) { - panic(fmt.Errorf("not implemented: IngestLicenses")) -} -func (c *arangoClient) CertifyLegal(ctx context.Context, certifyLegalSpec *model.CertifyLegalSpec) ([]*model.CertifyLegal, error) { - panic(fmt.Errorf("not implemented: CertifyLegal")) -} -func (c *arangoClient) IngestCertifyLegal(ctx context.Context, subject model.PackageOrSourceInput, declaredLicenses []*model.LicenseInputSpec, discoveredLicenses []*model.LicenseInputSpec, certifyLegal *model.CertifyLegalInputSpec) (*model.CertifyLegal, error) { - panic(fmt.Errorf("not implemented: IngestCertifyLegal")) -} -func (c *arangoClient) IngestCertifyLegals(ctx context.Context, subjects model.PackageOrSourceInputs, declaredLicensesList [][]*model.LicenseInputSpec, discoveredLicensesList [][]*model.LicenseInputSpec, certifyLegals []*model.CertifyLegalInputSpec) ([]*model.CertifyLegal, error) { - panic(fmt.Errorf("not implemented: IngestCertifyLegals")) -} - func ptrfromArangoSearchNGramStreamType(s driver.ArangoSearchNGramStreamType) *driver.ArangoSearchNGramStreamType { return &s } diff --git a/pkg/assembler/backends/arangodb/certifyBad.go b/pkg/assembler/backends/arangodb/certifyBad.go index aebe224fbf..72eb5e4aec 100644 --- a/pkg/assembler/backends/arangodb/certifyBad.go +++ b/pkg/assembler/backends/arangodb/certifyBad.go @@ -915,8 +915,8 @@ func getCertifyBadFromCursor(ctx context.Context, cursor driver.Cursor) ([]*mode certifyBad := &model.CertifyBad{ ID: createdValue.CertifyBadID, Justification: createdValue.Justification, - Origin: createdValue.Collector, - Collector: createdValue.Origin, + Origin: createdValue.Origin, + Collector: createdValue.Collector, } if pkg != nil { diff --git a/pkg/assembler/backends/arangodb/certifyLegal.go b/pkg/assembler/backends/arangodb/certifyLegal.go new file mode 100644 index 0000000000..3e4aa01d5d --- /dev/null +++ b/pkg/assembler/backends/arangodb/certifyLegal.go @@ -0,0 +1,882 @@ +// +// Copyright 2023 The GUAC Authors. +// +// 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 arangodb + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/arangodb/go-driver" + "github.com/guacsec/guac/pkg/assembler/graphql/model" +) + +func (c *arangoClient) CertifyLegal(ctx context.Context, certifyLegalSpec *model.CertifyLegalSpec) ([]*model.CertifyLegal, error) { + + var aqb *arangoQueryBuilder + if certifyLegalSpec.Subject != nil { + var combinedCertifyLegal []*model.CertifyLegal + if certifyLegalSpec.Subject.Package != nil { + values := map[string]any{} + // pkg certifyLegal + aqb = setPkgVersionMatchValues(certifyLegalSpec.Subject.Package, values) + aqb.forOutBound(certifyLegalPkgEdgesStr, "certifyLegal", "pVersion") + setCertifyLegalMatchValues(aqb, certifyLegalSpec, values) + + pkgCertifyLegals, err := getPkgCertifyLegalForQuery(ctx, c, aqb, values, + certifyLegalSpec.DeclaredLicenses, certifyLegalSpec.DiscoveredLicenses) + if err != nil { + return nil, fmt.Errorf("failed to retrieve package version certifyLegal with error: %w", err) + } + + combinedCertifyLegal = append(combinedCertifyLegal, pkgCertifyLegals...) + + } + if certifyLegalSpec.Subject.Source != nil { + values := map[string]any{} + aqb = setSrcMatchValues(certifyLegalSpec.Subject.Source, values) + aqb.forOutBound(certifyLegalSrcEdgesStr, "certifyLegal", "sName") + setCertifyLegalMatchValues(aqb, certifyLegalSpec, values) + + srcCertifyLegals, err := getSrcCertifyLegalForQuery(ctx, c, aqb, values, + certifyLegalSpec.DeclaredLicenses, certifyLegalSpec.DiscoveredLicenses) + if err != nil { + return nil, fmt.Errorf("failed to retrieve source certifyLegal with error: %w", err) + } + + combinedCertifyLegal = append(combinedCertifyLegal, srcCertifyLegals...) + } + return combinedCertifyLegal, nil + } + values := map[string]any{} + var combinedCertifyLegal []*model.CertifyLegal + + // pkg certifyLegal + aqb = newForQuery(certifyLegalsStr, "certifyLegal") + setCertifyLegalMatchValues(aqb, certifyLegalSpec, values) + aqb.forInBound(certifyLegalPkgEdgesStr, "pVersion", "certifyLegal") + aqb.forInBound(pkgHasVersionStr, "pName", "pVersion") + aqb.forInBound(pkgHasNameStr, "pNs", "pName") + aqb.forInBound(pkgHasNamespaceStr, "pType", "pNs") + + pkgCertifyLegals, err := getPkgCertifyLegalForQuery(ctx, c, aqb, values, + certifyLegalSpec.DeclaredLicenses, certifyLegalSpec.DiscoveredLicenses) + if err != nil { + return nil, fmt.Errorf("failed to retrieve package version certifyLegal with error: %w", err) + } + combinedCertifyLegal = append(combinedCertifyLegal, pkgCertifyLegals...) + + // get sources + values = map[string]any{} + aqb = newForQuery(certifyLegalsStr, "certifyLegal") + setCertifyLegalMatchValues(aqb, certifyLegalSpec, values) + aqb.forInBound(certifyLegalSrcEdgesStr, "sName", "certifyLegal") + aqb.forInBound(srcHasNameStr, "sNs", "sName") + aqb.forInBound(srcHasNamespaceStr, "sType", "sNs") + + srcCertifyLegals, err := getSrcCertifyLegalForQuery(ctx, c, aqb, values, + certifyLegalSpec.DeclaredLicenses, certifyLegalSpec.DiscoveredLicenses) + if err != nil { + return nil, fmt.Errorf("failed to retrieve source certifyLegal with error: %w", err) + } + combinedCertifyLegal = append(combinedCertifyLegal, srcCertifyLegals...) + + return combinedCertifyLegal, nil +} + +func getSrcCertifyLegalForQuery(ctx context.Context, c *arangoClient, + aqb *arangoQueryBuilder, values map[string]any, + decFilter, disFilter []*model.LicenseSpec) ([]*model.CertifyLegal, error) { + aqb.query.WriteString("\n") + aqb.query.WriteString(`RETURN { + 'srcName': { + 'type_id': sType._id, + 'type': sType.type, + 'namespace_id': sNs._id, + 'namespace': sNs.namespace, + 'name_id': sName._id, + 'name': sName.name, + 'commit': sName.commit, + 'tag': sName.tag + }, + 'certifyLegal_id': certifyLegal._id, + 'declaredLicense': certifyLegal.declaredLicense, + 'declaredLicenses': certifyLegal.declaredLicenses, + 'discoveredLicense': certifyLegal.discoveredLicense, + 'discoveredLicenses': certifyLegal.discoveredLicenses, + 'attribution': certifyLegal.attribution, + 'justification': certifyLegal.justification, + 'timeScanned': certifyLegal.timeScanned, + 'collector': certifyLegal.collector, + 'origin': certifyLegal.origin +}`) + + cursor, err := executeQueryWithRetry(ctx, c.db, aqb.string(), values, "CertifyLegal") + if err != nil { + return nil, fmt.Errorf("failed to query for CertifyLegal: %w", err) + } + defer cursor.Close() + + return c.getCertifyLegalFromCursor(ctx, cursor, decFilter, disFilter) +} + +func getPkgCertifyLegalForQuery(ctx context.Context, c *arangoClient, + aqb *arangoQueryBuilder, values map[string]any, + decFilter, disFilter []*model.LicenseSpec) ([]*model.CertifyLegal, error) { + aqb.query.WriteString("\n") + aqb.query.WriteString(`RETURN { + 'pkgVersion': { + 'type_id': pType._id, + 'type': pType.type, + 'namespace_id': pNs._id, + 'namespace': pNs.namespace, + 'name_id': pName._id, + 'name': pName.name, + 'version_id': pVersion._id, + 'version': pVersion.version, + 'subpath': pVersion.subpath, + 'qualifier_list': pVersion.qualifier_list + }, + 'certifyLegal_id': certifyLegal._id, + 'declaredLicense': certifyLegal.declaredLicense, + 'declaredLicenses': certifyLegal.declaredLicenses, + 'discoveredLicense': certifyLegal.discoveredLicense, + 'discoveredLicenses': certifyLegal.discoveredLicenses, + 'attribution': certifyLegal.attribution, + 'justification': certifyLegal.justification, + 'timeScanned': certifyLegal.timeScanned, + 'collector': certifyLegal.collector, + 'origin': certifyLegal.origin +}`) + + cursor, err := executeQueryWithRetry(ctx, c.db, aqb.string(), values, "CertifyLegal") + if err != nil { + return nil, fmt.Errorf("failed to query for CertifyLegal: %w", err) + } + defer cursor.Close() + + return c.getCertifyLegalFromCursor(ctx, cursor, decFilter, disFilter) +} + +func setCertifyLegalMatchValues(aqb *arangoQueryBuilder, certifyLegalSpec *model.CertifyLegalSpec, queryValues map[string]any) { + if certifyLegalSpec.ID != nil { + aqb.filter("certifyLegal", "_id", "==", "@id") + queryValues["id"] = *certifyLegalSpec.ID + } + if certifyLegalSpec.DeclaredLicense != nil { + aqb.filter("certifyLegal", "declaredLicense", "==", "@declaredLicense") + queryValues["declaredLicense"] = *certifyLegalSpec.DeclaredLicense + } + if certifyLegalSpec.DiscoveredLicense != nil { + aqb.filter("certifyLegal", "discoveredLicense", "==", "@discoveredLicense") + queryValues["discoveredLicense"] = *certifyLegalSpec.DiscoveredLicense + } + if certifyLegalSpec.Attribution != nil { + aqb.filter("certifyLegal", "attribution", "==", "@attribution") + queryValues["attribution"] = *certifyLegalSpec.Attribution + } + if certifyLegalSpec.Justification != nil { + aqb.filter("certifyLegal", justification, "==", "@"+justification) + queryValues[justification] = *certifyLegalSpec.Justification + } + if certifyLegalSpec.TimeScanned != nil { + aqb.filter("certifyLegal", "timeScanned", "==", "@timeScanned") + queryValues["timeScanned"] = certifyLegalSpec.TimeScanned.UTC() + } + if certifyLegalSpec.Origin != nil { + aqb.filter("certifyLegal", origin, "==", "@"+origin) + queryValues[origin] = *certifyLegalSpec.Origin + } + if certifyLegalSpec.Collector != nil { + aqb.filter("certifyLegal", collector, "==", "@"+collector) + queryValues[collector] = *certifyLegalSpec.Collector + } +} + +func getCertifyLegalQueryValues(pkg *model.PkgInputSpec, source *model.SourceInputSpec, dec []*model.License, dis []*model.License, certifyLegal *model.CertifyLegalInputSpec) map[string]any { + values := map[string]any{} + // add guac keys + if pkg != nil { + pkgId := guacPkgId(*pkg) + values["pkgVersionGuacKey"] = pkgId.VersionId + } else { + source := guacSrcId(*source) + values["srcNameGuacKey"] = source.NameId + } + + var decIDList []string + // KeyLists must be empty and not nil for use in the arango query. + decKeyList := []string{} + for _, l := range dec { + decIDList = append(decIDList, l.ID) + splitID := strings.Split(l.ID, "/") + decKeyList = append(decKeyList, splitID[1]) + } + values["declaredLicenses"] = decIDList + values["declaredLicensesKeyList"] = decKeyList + + var disIDList []string + disKeyList := []string{} + for _, l := range dis { + disIDList = append(disIDList, l.ID) + splitID := strings.Split(l.ID, "/") + disKeyList = append(disKeyList, splitID[1]) + } + values["discoveredLicenses"] = disIDList + values["discoveredLicensesKeyList"] = disKeyList + + values["declaredLicense"] = certifyLegal.DeclaredLicense + values["discoveredLicense"] = certifyLegal.DiscoveredLicense + values["attribution"] = certifyLegal.Attribution + values["justification"] = certifyLegal.Justification + values["timeScanned"] = certifyLegal.TimeScanned.UTC() + values["origin"] = certifyLegal.Origin + values["collector"] = certifyLegal.Collector + + return values +} + +func (c *arangoClient) IngestCertifyLegal( + ctx context.Context, + subject model.PackageOrSourceInput, + declaredLicenses []*model.LicenseInputSpec, + discoveredLicenses []*model.LicenseInputSpec, + certifyLegal *model.CertifyLegalInputSpec) (*model.CertifyLegal, error) { + + dec, err := c.getLicenses(ctx, declaredLicenses) + if err != nil { + return nil, fmt.Errorf("failed to get declared licenses list with error: %w", err) + } + + dis, err := c.getLicenses(ctx, discoveredLicenses) + if err != nil { + return nil, fmt.Errorf("failed to get discovered licenses list with error: %w", err) + } + + if subject.Package != nil { + query := ` +LET firstPkg = FIRST( + FOR pVersion in pkgVersions + FILTER pVersion.guacKey == @pkgVersionGuacKey + FOR pName in pkgNames + FILTER pName._id == pVersion._parent + FOR pNs in pkgNamespaces + FILTER pNs._id == pName._parent + FOR pType in pkgTypes + FILTER pType._id == pNs._parent + + RETURN { + 'typeID': pType._id, + 'type': pType.type, + 'namespace_id': pNs._id, + 'namespace': pNs.namespace, + 'name_id': pName._id, + 'name': pName.name, + 'version_id': pVersion._id, + 'version': pVersion.version, + 'subpath': pVersion.subpath, + 'qualifier_list': pVersion.qualifier_list, + 'versionDoc': pVersion + } +) + +LET certifyLegal = FIRST( + UPSERT { + packageID:firstPkg.version_id, + declaredLicense:@declaredLicense, + declaredLicenses:@declaredLicenses, + discoveredLicense:@discoveredLicense, + discoveredLicenses:@discoveredLicenses, + attribution:@attribution, + justification:@justification, + timeScanned:@timeScanned, + collector:@collector, + origin:@origin + } + INSERT { + packageID:firstPkg.version_id, + declaredLicense:@declaredLicense, + declaredLicenses:@declaredLicenses, + discoveredLicense:@discoveredLicense, + discoveredLicenses:@discoveredLicenses, + attribution:@attribution, + justification:@justification, + timeScanned:@timeScanned, + collector:@collector, + origin:@origin + } + UPDATE {} IN certifyLegals + RETURN NEW +) + +LET edgeCollection = ( + INSERT { _key: CONCAT("certifyLegalPkgEdges", firstPkg.versionDoc._key, certifyLegal._key), _from: firstPkg.version_id, _to: certifyLegal._id } INTO certifyLegalPkgEdges OPTIONS { overwriteMode: "ignore" } +) + +LET declaredLicensesCollection = (FOR decData IN @declaredLicensesKeyList + INSERT { _key: CONCAT("certifyLegalDeclaredLicensesEdges", certifyLegal._key, decData), _from: certifyLegal._id, _to: CONCAT("licenses/", decData) } INTO certifyLegalDeclaredLicensesEdges OPTIONS { overwriteMode: "ignore" } +) + +LET discoveredLicensesCollection = (FOR disData IN @discoveredLicensesKeyList + INSERT { _key: CONCAT("certifyLegalDiscoveredLicensesEdges", certifyLegal._key, disData), _from: certifyLegal._id, _to: CONCAT("licenses/", disData) } INTO certifyLegalDiscoveredLicensesEdges OPTIONS { overwriteMode: "ignore" } +) + +RETURN { + 'pkgVersion': { + 'type_id': firstPkg.typeID, + 'type': firstPkg.type, + 'namespace_id': firstPkg.namespace_id, + 'namespace': firstPkg.namespace, + 'name_id': firstPkg.name_id, + 'name': firstPkg.name, + 'version_id': firstPkg.version_id, + 'version': firstPkg.version, + 'subpath': firstPkg.subpath, + 'qualifier_list': firstPkg.qualifier_list + }, + 'certifyLegal_id': certifyLegal._id, + 'declaredLicense': certifyLegal.declaredLicense, + 'declaredLicenses': certifyLegal.declaredLicenses, + 'discoveredLicense': certifyLegal.discoveredLicense, + 'discoveredLicenses': certifyLegal.discoveredLicenses, + 'attribution': certifyLegal.attribution, + 'justification': certifyLegal.justification, + 'timeScanned': certifyLegal.timeScanned, + 'collector': certifyLegal.collector, + 'origin': certifyLegal.origin +}` + + cursor, err := executeQueryWithRetry(ctx, c.db, query, getCertifyLegalQueryValues(subject.Package, nil, dec, dis, certifyLegal), "IngestCertifyLegal - Pkg") + if err != nil { + return nil, fmt.Errorf("failed to ingest package certifyLegal: %w", err) + } + defer cursor.Close() + + certifyLegalList, err := c.getCertifyLegalFromCursor(ctx, cursor, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to get certifyLegals from arango cursor: %w", err) + } + + if len(certifyLegalList) == 1 { + return certifyLegalList[0], nil + } + return nil, fmt.Errorf("number of certifyLegal ingested is greater than one") + } + + if subject.Source != nil { + query := ` +LET firstSrc = FIRST( + FOR sName in srcNames + FILTER sName.guacKey == @srcNameGuacKey + FOR sNs in srcNamespaces + FILTER sNs._id == sName._parent + FOR sType in srcTypes + FILTER sType._id == sNs._parent + + RETURN { + 'typeID': sType._id, + 'type': sType.type, + 'namespace_id': sNs._id, + 'namespace': sNs.namespace, + 'name_id': sName._id, + 'name': sName.name, + 'commit': sName.commit, + 'tag': sName.tag, + 'nameDoc': sName + } +) + +LET certifyLegal = FIRST( + UPSERT { + sourceID:firstSrc.name_id, + declaredLicense:@declaredLicense, + declaredLicenses:@declaredLicenses, + discoveredLicense:@discoveredLicense, + discoveredLicenses:@discoveredLicenses, + attribution:@attribution, + justification:@justification, + timeScanned:@timeScanned, + collector:@collector, + origin:@origin + } + INSERT { + sourceID:firstSrc.name_id, + declaredLicense:@declaredLicense, + declaredLicenses:@declaredLicenses, + discoveredLicense:@discoveredLicense, + discoveredLicenses:@discoveredLicenses, + attribution:@attribution, + justification:@justification, + timeScanned:@timeScanned, + collector:@collector, + origin:@origin + } + UPDATE {} IN certifyLegals + RETURN NEW +) + +LET edgeCollection = ( + INSERT { _key: CONCAT("certifyLegalSrcEdges", firstSrc.nameDoc._key, certifyLegal._key), _from: firstSrc.name_id, _to: certifyLegal._id } INTO certifyLegalSrcEdges OPTIONS { overwriteMode: "ignore" } +) + +LET declaredLicensesCollection = (FOR decData IN @declaredLicensesKeyList + INSERT { _key: CONCAT("certifyLegalDeclaredLicensesEdges", certifyLegal._key, decData), _from: certifyLegal._id, _to: CONCAT("licenses/", decData) } INTO certifyLegalDeclaredLicensesEdges OPTIONS { overwriteMode: "ignore" } +) + +LET discoveredLicensesCollection = (FOR disData IN @discoveredLicensesKeyList + INSERT { _key: CONCAT("certifyLegalDiscoveredLicensesEdges", certifyLegal._key, disData), _from: certifyLegal._id, _to: CONCAT("licenses/", disData) } INTO certifyLegalDiscoveredLicensesEdges OPTIONS { overwriteMode: "ignore" } +) + +RETURN { + 'srcName': { + 'type_id': firstSrc.typeID, + 'type': firstSrc.type, + 'namespace_id': firstSrc.namespace_id, + 'namespace': firstSrc.namespace, + 'name_id': firstSrc.name_id, + 'name': firstSrc.name, + 'commit': firstSrc.commit, + 'tag': firstSrc.tag + }, + 'certifyLegal_id': certifyLegal._id, + 'declaredLicense': certifyLegal.declaredLicense, + 'declaredLicenses': certifyLegal.declaredLicenses, + 'discoveredLicense': certifyLegal.discoveredLicense, + 'discoveredLicenses': certifyLegal.discoveredLicenses, + 'attribution': certifyLegal.attribution, + 'justification': certifyLegal.justification, + 'timeScanned': certifyLegal.timeScanned, + 'collector': certifyLegal.collector, + 'origin': certifyLegal.origin +}` + + cursor, err := executeQueryWithRetry(ctx, c.db, query, getCertifyLegalQueryValues(nil, subject.Source, dec, dis, certifyLegal), "IngestCertifyLegal - source") + if err != nil { + return nil, fmt.Errorf("failed to ingest source certifyLegal: %w", err) + } + defer cursor.Close() + + certifyLegalList, err := c.getCertifyLegalFromCursor(ctx, cursor, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to get certifyLegals from arango cursor: %w", err) + } + + if len(certifyLegalList) == 1 { + return certifyLegalList[0], nil + } + return nil, fmt.Errorf("number of certifyLegal ingested is greater than one") + } + return nil, fmt.Errorf("package or source is not specified for IngestCertifyLegal") +} + +func (c *arangoClient) IngestCertifyLegals( + ctx context.Context, + subjects model.PackageOrSourceInputs, + declaredLicensesList [][]*model.LicenseInputSpec, + discoveredLicensesList [][]*model.LicenseInputSpec, + certifyLegals []*model.CertifyLegalInputSpec) ([]*model.CertifyLegal, error) { + + if len(subjects.Packages) > 0 { + var listOfValues []map[string]any + + for i := range subjects.Packages { + dec, err := c.getLicenses(ctx, declaredLicensesList[i]) + if err != nil { + return nil, fmt.Errorf("failed to get declared licenses list with error: %w", err) + } + + dis, err := c.getLicenses(ctx, discoveredLicensesList[i]) + if err != nil { + return nil, fmt.Errorf("failed to get discovered licenses list with error: %w", err) + } + + listOfValues = append(listOfValues, getCertifyLegalQueryValues(subjects.Packages[i], nil, dec, dis, certifyLegals[i])) + } + + var documents []string + for _, val := range listOfValues { + bs, _ := json.Marshal(val) + documents = append(documents, string(bs)) + } + + queryValues := map[string]any{} + queryValues["documents"] = fmt.Sprint(strings.Join(documents, ",")) + + var sb strings.Builder + + sb.WriteString("for doc in [") + for i, val := range listOfValues { + bs, _ := json.Marshal(val) + if i == len(listOfValues)-1 { + sb.WriteString(string(bs)) + } else { + sb.WriteString(string(bs) + ",") + } + } + sb.WriteString("]") + + query := ` +LET firstPkg = FIRST( + FOR pVersion in pkgVersions + FILTER pVersion.guacKey == doc.pkgVersionGuacKey + FOR pName in pkgNames + FILTER pName._id == pVersion._parent + FOR pNs in pkgNamespaces + FILTER pNs._id == pName._parent + FOR pType in pkgTypes + FILTER pType._id == pNs._parent + + RETURN { + 'typeID': pType._id, + 'type': pType.type, + 'namespace_id': pNs._id, + 'namespace': pNs.namespace, + 'name_id': pName._id, + 'name': pName.name, + 'version_id': pVersion._id, + 'version': pVersion.version, + 'subpath': pVersion.subpath, + 'qualifier_list': pVersion.qualifier_list, + 'versionDoc': pVersion + } +) + +LET certifyLegal = FIRST( + UPSERT { + packageID:firstPkg.version_id, + declaredLicense:doc.declaredLicense, + declaredLicenses:doc.declaredLicenses, + discoveredLicense:doc.discoveredLicense, + discoveredLicenses:doc.discoveredLicenses, + attribution:doc.attribution, + justification:doc.justification, + timeScanned:doc.timeScanned, + collector:doc.collector, + origin:doc.origin + } + INSERT { + packageID:firstPkg.version_id, + declaredLicense:doc.declaredLicense, + declaredLicenses:doc.declaredLicenses, + discoveredLicense:doc.discoveredLicense, + discoveredLicenses:doc.discoveredLicenses, + attribution:doc.attribution, + justification:doc.justification, + timeScanned:doc.timeScanned, + collector:doc.collector, + origin:doc.origin + } + UPDATE {} IN certifyLegals + RETURN NEW +) + +LET edgeCollection = ( + INSERT { _key: CONCAT("certifyLegalPkgEdges", firstPkg.versionDoc._key, certifyLegal._key), _from: firstPkg.version_id, _to: certifyLegal._id } INTO certifyLegalPkgEdges OPTIONS { overwriteMode: "ignore" } +) + +LET declaredLicensesCollection = (FOR decData IN doc.declaredLicensesKeyList + INSERT { _key: CONCAT("certifyLegalDeclaredLicensesEdges", certifyLegal._key, decData), _from: certifyLegal._id, _to: CONCAT("licenses/", decData) } INTO certifyLegalDeclaredLicensesEdges OPTIONS { overwriteMode: "ignore" } +) + +LET discoveredLicensesCollection = (FOR disData IN doc.discoveredLicensesKeyList + INSERT { _key: CONCAT("certifyLegalDiscoveredLicensesEdges", certifyLegal._key, disData), _from: certifyLegal._id, _to: CONCAT("licenses/", disData) } INTO certifyLegalDiscoveredLicensesEdges OPTIONS { overwriteMode: "ignore" } +) + +RETURN { + 'pkgVersion': { + 'type_id': firstPkg.typeID, + 'type': firstPkg.type, + 'namespace_id': firstPkg.namespace_id, + 'namespace': firstPkg.namespace, + 'name_id': firstPkg.name_id, + 'name': firstPkg.name, + 'version_id': firstPkg.version_id, + 'version': firstPkg.version, + 'subpath': firstPkg.subpath, + 'qualifier_list': firstPkg.qualifier_list + }, + 'certifyLegal_id': certifyLegal._id, + 'declaredLicense': certifyLegal.declaredLicense, + 'declaredLicenses': certifyLegal.declaredLicenses, + 'discoveredLicense': certifyLegal.discoveredLicense, + 'discoveredLicenses': certifyLegal.discoveredLicenses, + 'attribution': certifyLegal.attribution, + 'justification': certifyLegal.justification, + 'timeScanned': certifyLegal.timeScanned, + 'collector': certifyLegal.collector, + 'origin': certifyLegal.origin +}` + + sb.WriteString(query) + + cursor, err := executeQueryWithRetry(ctx, c.db, sb.String(), nil, "IngestCertifyLegals - Pkg") + if err != nil { + return nil, fmt.Errorf("failed to ingest package certifyLegals: %w", err) + } + defer cursor.Close() + + certifyLegalList, err := c.getCertifyLegalFromCursor(ctx, cursor, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to get certifyLegals from arango cursor: %w", err) + } + + return certifyLegalList, nil + } + if len(subjects.Sources) > 0 { + var listOfValues []map[string]any + + for i := range subjects.Sources { + dec, err := c.getLicenses(ctx, declaredLicensesList[i]) + if err != nil { + return nil, fmt.Errorf("failed to get declared licenses list with error: %w", err) + } + + dis, err := c.getLicenses(ctx, discoveredLicensesList[i]) + if err != nil { + return nil, fmt.Errorf("failed to get discovered licenses list with error: %w", err) + } + + listOfValues = append(listOfValues, getCertifyLegalQueryValues(nil, subjects.Sources[i], dec, dis, certifyLegals[i])) + } + + var documents []string + for _, val := range listOfValues { + bs, _ := json.Marshal(val) + documents = append(documents, string(bs)) + } + + queryValues := map[string]any{} + queryValues["documents"] = fmt.Sprint(strings.Join(documents, ",")) + + var sb strings.Builder + + sb.WriteString("for doc in [") + for i, val := range listOfValues { + bs, _ := json.Marshal(val) + if i == len(listOfValues)-1 { + sb.WriteString(string(bs)) + } else { + sb.WriteString(string(bs) + ",") + } + } + sb.WriteString("]") + + query := ` +LET firstSrc = FIRST( + FOR sName in srcNames + FILTER sName.guacKey == doc.srcNameGuacKey + FOR sNs in srcNamespaces + FILTER sNs._id == sName._parent + FOR sType in srcTypes + FILTER sType._id == sNs._parent + + RETURN { + 'typeID': sType._id, + 'type': sType.type, + 'namespace_id': sNs._id, + 'namespace': sNs.namespace, + 'name_id': sName._id, + 'name': sName.name, + 'commit': sName.commit, + 'tag': sName.tag, + 'nameDoc': sName + } +) + +LET certifyLegal = FIRST( + UPSERT { + sourceID:firstSrc.name_id, + declaredLicense:doc.declaredLicense, + declaredLicenses:doc.declaredLicenses, + discoveredLicense:doc.discoveredLicense, + discoveredLicenses:doc.discoveredLicenses, + attribution:doc.attribution, + justification:doc.justification, + timeScanned:doc.timeScanned, + collector:doc.collector, + origin:doc.origin + } + INSERT { + sourceID:firstSrc.name_id, + declaredLicense:doc.declaredLicense, + declaredLicenses:doc.declaredLicenses, + discoveredLicense:doc.discoveredLicense, + discoveredLicenses:doc.discoveredLicenses, + attribution:doc.attribution, + justification:doc.justification, + timeScanned:doc.timeScanned, + collector:doc.collector, + origin:doc.origin + } + UPDATE {} IN certifyLegals + RETURN NEW +) + +LET edgeCollection = ( + INSERT { _key: CONCAT("certifyLegalSrcEdges", firstSrc.nameDoc._key, certifyLegal._key), _from: firstSrc.name_id, _to: certifyLegal._id } INTO certifyLegalSrcEdges OPTIONS { overwriteMode: "ignore" } +) + +LET declaredLicensesCollection = (FOR decData IN doc.declaredLicensesKeyList + INSERT { _key: CONCAT("certifyLegalDeclaredLicensesEdges", certifyLegal._key, decData), _from: certifyLegal._id, _to: CONCAT("licenses/", decData) } INTO certifyLegalDeclaredLicensesEdges OPTIONS { overwriteMode: "ignore" } +) + +LET discoveredLicensesCollection = (FOR disData IN doc.discoveredLicensesKeyList + INSERT { _key: CONCAT("certifyLegalDiscoveredLicensesEdges", certifyLegal._key, disData), _from: certifyLegal._id, _to: CONCAT("licenses/", disData) } INTO certifyLegalDiscoveredLicensesEdges OPTIONS { overwriteMode: "ignore" } +) + +RETURN { + 'srcName': { + 'type_id': firstSrc.typeID, + 'type': firstSrc.type, + 'namespace_id': firstSrc.namespace_id, + 'namespace': firstSrc.namespace, + 'name_id': firstSrc.name_id, + 'name': firstSrc.name, + 'commit': firstSrc.commit, + 'tag': firstSrc.tag + }, + 'certifyLegal_id': certifyLegal._id, + 'declaredLicense': certifyLegal.declaredLicense, + 'declaredLicenses': certifyLegal.declaredLicenses, + 'discoveredLicense': certifyLegal.discoveredLicense, + 'discoveredLicenses': certifyLegal.discoveredLicenses, + 'attribution': certifyLegal.attribution, + 'justification': certifyLegal.justification, + 'timeScanned': certifyLegal.timeScanned, + 'collector': certifyLegal.collector, + 'origin': certifyLegal.origin +}` + + sb.WriteString(query) + + cursor, err := executeQueryWithRetry(ctx, c.db, sb.String(), nil, "IngestCertifyLegals - source") + if err != nil { + return nil, fmt.Errorf("failed to ingest source certifyLegal: %w", err) + } + defer cursor.Close() + certifyLegalList, err := c.getCertifyLegalFromCursor(ctx, cursor, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to get certifyLegals from arango cursor: %w", err) + } + + return certifyLegalList, nil + + } + return nil, fmt.Errorf("packages or sources not specified for IngestCertifyLegals") +} + +func (c *arangoClient) getCertifyLegalFromCursor(ctx context.Context, + cursor driver.Cursor, decFilter, disFilter []*model.LicenseSpec) ( + []*model.CertifyLegal, error) { + type collectedData struct { + Pkg *dbPkgVersion `json:"pkgVersion"` + SrcName *dbSrcName `json:"srcName"` + CertifyLegalID string `json:"certifyLegal_id"` + DeclaredLicense string `json:"declaredLicense"` + DeclaredLicenses []string `json:"declaredLicenses"` + DiscoveredLicense string `json:"discoveredLicense"` + DiscoveredLicenses []string `json:"discoveredLicenses"` + Attribution string `json:"attribution"` + Justification string `json:"justification"` + TimeScanned time.Time `json:"timeScanned"` + Collector string `json:"collector"` + Origin string `json:"origin"` + } + + var createdValues []collectedData + for { + var doc collectedData + _, err := cursor.ReadDocument(ctx, &doc) + if err != nil { + if driver.IsNoMoreDocuments(err) { + break + } else { + return nil, fmt.Errorf("failed to certifyLegal from cursor: %w", err) + } + } else { + createdValues = append(createdValues, doc) + } + } + + var certifyLegalList []*model.CertifyLegal + for _, createdValue := range createdValues { + var pkg *model.Package = nil + var src *model.Source = nil + if createdValue.Pkg != nil { + pkg = generateModelPackage( + createdValue.Pkg.TypeID, + createdValue.Pkg.PkgType, + createdValue.Pkg.NamespaceID, + createdValue.Pkg.Namespace, + createdValue.Pkg.NameID, + createdValue.Pkg.Name, + createdValue.Pkg.VersionID, + createdValue.Pkg.Version, + createdValue.Pkg.Subpath, + createdValue.Pkg.QualifierList) + } else if createdValue.SrcName != nil { + src = generateModelSource( + createdValue.SrcName.TypeID, + createdValue.SrcName.SrcType, + createdValue.SrcName.NamespaceID, + createdValue.SrcName.Namespace, + createdValue.SrcName.NameID, + createdValue.SrcName.Name, + createdValue.SrcName.Commit, + createdValue.SrcName.Tag) + } + + certifyLegal := &model.CertifyLegal{ + ID: createdValue.CertifyLegalID, + DeclaredLicense: createdValue.DeclaredLicense, + DiscoveredLicense: createdValue.DiscoveredLicense, + Attribution: createdValue.Attribution, + Justification: createdValue.Justification, + TimeScanned: createdValue.TimeScanned, + Origin: createdValue.Origin, + Collector: createdValue.Collector, + } + + dec, err := c.getLicensesByID(ctx, createdValue.DeclaredLicenses) + if err != nil { + return nil, fmt.Errorf("failed to convert Declared License IDs into nodes: %w", err) + } + certifyLegal.DeclaredLicenses = dec + + dis, err := c.getLicensesByID(ctx, createdValue.DiscoveredLicenses) + if err != nil { + return nil, fmt.Errorf("failed to convert Discovered License IDs into nodes: %w", err) + } + certifyLegal.DiscoveredLicenses = dis + + // NOTE: Matching the filter list to the license list on queries is done + // here, not in the Arango query. + if !licenseMatch(decFilter, dec) || !licenseMatch(disFilter, dis) { + continue + } + + if pkg != nil { + certifyLegal.Subject = pkg + } else if src != nil { + certifyLegal.Subject = src + } else { + return nil, fmt.Errorf("failed to get subject from cursor for certifyLegal") + } + certifyLegalList = append(certifyLegalList, certifyLegal) + } + return certifyLegalList, nil +} diff --git a/pkg/assembler/backends/arangodb/certifyLegal_test.go b/pkg/assembler/backends/arangodb/certifyLegal_test.go new file mode 100644 index 0000000000..5dfb948f61 --- /dev/null +++ b/pkg/assembler/backends/arangodb/certifyLegal_test.go @@ -0,0 +1,689 @@ +// +// Copyright 2023 The GUAC Authors. +// +// 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. + +//go:build integration + +package arangodb + +import ( + "context" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/guacsec/guac/internal/testing/ptrfrom" + "github.com/guacsec/guac/internal/testing/testdata" + "github.com/guacsec/guac/pkg/assembler/graphql/model" +) + +// var pNone = &model.PkgInputSpec{ +// Type: "none", +// Name: "none", +// } + +// var sNone = &model.SourceInputSpec{ +// Type: "none", +// Namespace: "github.com/nope", +// Name: "none", +// } + +// var lNone = &model.LicenseInputSpec{ +// Name: "LIC_NONE", +// ListVersion: ptrfrom.String("1.2.3"), +// } + +func TestLegal(t *testing.T) { + type call struct { + PkgSrc model.PackageOrSourceInput + Dec []*model.LicenseInputSpec + Dis []*model.LicenseInputSpec + Legal *model.CertifyLegalInputSpec + } + tests := []struct { + Name string + InPkg []*model.PkgInputSpec + InSrc []*model.SourceInputSpec + InLic []*model.LicenseInputSpec + Calls []call + IDInFilter int + Query *model.CertifyLegalSpec + ExpLegal []*model.CertifyLegal + ExpIngestErr bool + ExpQueryErr bool + }{ + { + Name: "HappyPath", + InPkg: []*model.PkgInputSpec{testdata.P1}, + InLic: []*model.LicenseInputSpec{testdata.L1}, + Calls: []call{ + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P1, + }, + Dec: []*model.LicenseInputSpec{testdata.L1}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification", + }, + }, + }, + Query: &model.CertifyLegalSpec{ + Justification: ptrfrom.String("test justification"), + }, + ExpLegal: []*model.CertifyLegal{ + { + Subject: testdata.P1out, + DeclaredLicenses: []*model.License{testdata.L1out}, + Justification: "test justification", + }, + }, + }, + { + Name: "Ingest same twice", + InPkg: []*model.PkgInputSpec{testdata.P1}, + InLic: []*model.LicenseInputSpec{testdata.L1}, + Calls: []call{ + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P1, + }, + Dec: []*model.LicenseInputSpec{testdata.L1}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification", + }, + }, + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P1, + }, + Dec: []*model.LicenseInputSpec{testdata.L1}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification", + }, + }, + }, + Query: &model.CertifyLegalSpec{ + Justification: ptrfrom.String("test justification"), + }, + ExpLegal: []*model.CertifyLegal{ + { + Subject: testdata.P1out, + DeclaredLicenses: []*model.License{testdata.L1out}, + Justification: "test justification", + }, + }, + }, + { + Name: "Query on Justification", + InPkg: []*model.PkgInputSpec{testdata.P1}, + InLic: []*model.LicenseInputSpec{testdata.L1}, + Calls: []call{ + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P1, + }, + Dec: []*model.LicenseInputSpec{testdata.L1}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification", + }, + }, + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P1, + }, + Dec: []*model.LicenseInputSpec{testdata.L1}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification 2", + }, + }, + }, + Query: &model.CertifyLegalSpec{ + Justification: ptrfrom.String("test justification 2"), + }, + ExpLegal: []*model.CertifyLegal{ + { + Subject: testdata.P1out, + DeclaredLicenses: []*model.License{testdata.L1out}, + Justification: "test justification 2", + }, + }, + }, + { + Name: "Query on ID", + InPkg: []*model.PkgInputSpec{testdata.P1}, + InLic: []*model.LicenseInputSpec{testdata.L1}, + Calls: []call{ + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P1, + }, + Dec: []*model.LicenseInputSpec{testdata.L1}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification", + }, + }, + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P1, + }, + Dec: []*model.LicenseInputSpec{testdata.L1}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification 2", + }, + }, + }, + IDInFilter: 2, + Query: &model.CertifyLegalSpec{}, + ExpLegal: []*model.CertifyLegal{ + { + Subject: testdata.P1out, + DeclaredLicenses: []*model.License{testdata.L1out}, + Justification: "test justification 2", + }, + }, + }, + { + Name: "Query on Package", + InPkg: []*model.PkgInputSpec{testdata.P1, testdata.P2}, + InLic: []*model.LicenseInputSpec{testdata.L1}, + Calls: []call{ + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P1, + }, + Dec: []*model.LicenseInputSpec{testdata.L1}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification", + }, + }, + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P2, + }, + Dec: []*model.LicenseInputSpec{testdata.L1}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification", + }, + }, + }, + Query: &model.CertifyLegalSpec{ + Subject: &model.PackageOrSourceSpec{ + Package: &model.PkgSpec{ + Version: ptrfrom.String("2.11.1"), + }, + }, + }, + ExpLegal: []*model.CertifyLegal{ + { + Subject: testdata.P2out, + DeclaredLicenses: []*model.License{testdata.L1out}, + Justification: "test justification", + }, + }, + }, + { + Name: "Query on Source", + InSrc: []*model.SourceInputSpec{testdata.S1, testdata.S2}, + InLic: []*model.LicenseInputSpec{testdata.L1}, + Calls: []call{ + { + PkgSrc: model.PackageOrSourceInput{ + Source: testdata.S1, + }, + Dec: []*model.LicenseInputSpec{testdata.L1}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification", + }, + }, + { + PkgSrc: model.PackageOrSourceInput{ + Source: testdata.S2, + }, + Dec: []*model.LicenseInputSpec{testdata.L1}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification", + }, + }, + }, + Query: &model.CertifyLegalSpec{ + Subject: &model.PackageOrSourceSpec{ + Source: &model.SourceSpec{ + Name: ptrfrom.String("myrepo"), + }, + }, + }, + ExpLegal: []*model.CertifyLegal{ + { + Subject: testdata.S1out, + DeclaredLicenses: []*model.License{testdata.L1out}, + Justification: "test justification", + }, + }, + }, + { + Name: "Query on License", + InSrc: []*model.SourceInputSpec{testdata.S1}, + InLic: []*model.LicenseInputSpec{testdata.L1, testdata.L2, testdata.L3}, + Calls: []call{ + { + PkgSrc: model.PackageOrSourceInput{ + Source: testdata.S1, + }, + Dec: []*model.LicenseInputSpec{testdata.L1, testdata.L2}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification", + }, + }, + { + PkgSrc: model.PackageOrSourceInput{ + Source: testdata.S1, + }, + Dec: []*model.LicenseInputSpec{testdata.L3}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification", + }, + }, + }, + Query: &model.CertifyLegalSpec{ + DeclaredLicenses: []*model.LicenseSpec{ + {Name: ptrfrom.String("GPL-2.0-or-later")}, + }, + }, + ExpLegal: []*model.CertifyLegal{ + { + Subject: testdata.S1out, + DeclaredLicenses: []*model.License{testdata.L1out, testdata.L2out}, + Justification: "test justification", + }, + }, + }, + { + Name: "Query on License inline", + InSrc: []*model.SourceInputSpec{testdata.S1}, + InLic: []*model.LicenseInputSpec{testdata.L1, testdata.L2, testdata.L4}, + Calls: []call{ + { + PkgSrc: model.PackageOrSourceInput{ + Source: testdata.S1, + }, + Dec: []*model.LicenseInputSpec{testdata.L1, testdata.L4}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification", + }, + }, + { + PkgSrc: model.PackageOrSourceInput{ + Source: testdata.S1, + }, + Dec: []*model.LicenseInputSpec{testdata.L2}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification", + }, + }, + }, + Query: &model.CertifyLegalSpec{ + DeclaredLicenses: []*model.LicenseSpec{ + {Inline: &testdata.InlineLicense}, + }, + }, + ExpLegal: []*model.CertifyLegal{ + { + Subject: testdata.S1out, + DeclaredLicenses: []*model.License{testdata.L1out, testdata.L4out}, + Justification: "test justification", + }, + }, + }, + { + Name: "Query on expression", + InPkg: []*model.PkgInputSpec{testdata.P1}, + Calls: []call{ + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P1, + }, + Legal: &model.CertifyLegalInputSpec{ + DeclaredLicense: "GPL OR MIT", + }, + }, + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P1, + }, + Legal: &model.CertifyLegalInputSpec{ + DeclaredLicense: "GPL AND MIT", + }, + }, + }, + Query: &model.CertifyLegalSpec{ + DeclaredLicense: ptrfrom.String("GPL AND MIT"), + }, + ExpLegal: []*model.CertifyLegal{ + { + Subject: testdata.P1out, + DeclaredLicense: "GPL AND MIT", + }, + }, + }, + { + Name: "Query on attribution", + InPkg: []*model.PkgInputSpec{testdata.P1}, + Calls: []call{ + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P1, + }, + Legal: &model.CertifyLegalInputSpec{ + Attribution: "Copyright Jeff", + }, + }, + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P1, + }, + Legal: &model.CertifyLegalInputSpec{ + Attribution: "Copyright Bob", + }, + }, + }, + Query: &model.CertifyLegalSpec{ + Attribution: ptrfrom.String("Copyright Jeff"), + }, + ExpLegal: []*model.CertifyLegal{ + { + Subject: testdata.P1out, + Attribution: "Copyright Jeff", + }, + }, + }, + { + Name: "Query on time", + InPkg: []*model.PkgInputSpec{testdata.P1}, + Calls: []call{ + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P1, + }, + Legal: &model.CertifyLegalInputSpec{ + TimeScanned: testdata.T3, + }, + }, + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P1, + }, + Legal: &model.CertifyLegalInputSpec{ + TimeScanned: testdata.T2, + }, + }, + }, + Query: &model.CertifyLegalSpec{ + TimeScanned: &testdata.T2, + }, + ExpLegal: []*model.CertifyLegal{ + { + Subject: testdata.P1out, + TimeScanned: testdata.T2, + }, + }, + }, + { + Name: "Query multiple", + InPkg: []*model.PkgInputSpec{testdata.P1, testdata.P2, testdata.P3}, + InLic: []*model.LicenseInputSpec{testdata.L1}, + Calls: []call{ + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P1, + }, + Dec: []*model.LicenseInputSpec{testdata.L1}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification special", + }, + }, + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P2, + }, + Dec: []*model.LicenseInputSpec{testdata.L1}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification special", + }, + }, + { + PkgSrc: model.PackageOrSourceInput{ + Package: testdata.P3, + }, + Dec: []*model.LicenseInputSpec{testdata.L1}, + Legal: &model.CertifyLegalInputSpec{ + Justification: "test justification other", + }, + }, + }, + Query: &model.CertifyLegalSpec{ + Justification: ptrfrom.String("test justification special"), + }, + ExpLegal: []*model.CertifyLegal{ + { + Subject: testdata.P1out, + DeclaredLicenses: []*model.License{testdata.L1out}, + Justification: "test justification special", + }, + { + Subject: testdata.P2out, + DeclaredLicenses: []*model.License{testdata.L1out}, + Justification: "test justification special", + }, + }, + }, + // { + // Name: "Ingest without Package", + // Calls: []call{ + // { + // PkgSrc: model.PackageOrSourceInput{ + // Package: pNone, + // }, + // Legal: &model.CertifyLegalInputSpec{}, + // }, + // }, + // ExpIngestErr: true, + // }, + // { + // Name: "Ingest without Source", + // Calls: []call{ + // { + // PkgSrc: model.PackageOrSourceInput{ + // Source: sNone, + // }, + // Legal: &model.CertifyLegalInputSpec{}, + // }, + // }, + // ExpIngestErr: true, + // }, + // { + // Name: "Ingest without License", + // InPkg: []*model.PkgInputSpec{testdata.P1}, + // Calls: []call{ + // { + // PkgSrc: model.PackageOrSourceInput{ + // Package: testdata.P1, + // }, + // Dec: []*model.LicenseInputSpec{lNone}, + // Legal: &model.CertifyLegalInputSpec{}, + // }, + // }, + // ExpIngestErr: true, + // }, + } + ignoreID := cmp.FilterPath(func(p cmp.Path) bool { + return strings.Compare(".ID", p[len(p)-1].String()) == 0 + }, cmp.Ignore()) + ctx := context.Background() + arangArg := getArangoConfig() + err := deleteDatabase(ctx, arangArg) + if err != nil { + t.Fatalf("error deleting arango database: %v", err) + } + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + b, err := getBackend(ctx, arangArg) + if err != nil { + t.Fatalf("Could not instantiate testing backend: %v", err) + } + for _, p := range test.InPkg { + if _, err := b.IngestPackage(ctx, *p); err != nil { + t.Fatalf("Could not ingest package: %v", err) + } + } + for _, s := range test.InSrc { + if _, err := b.IngestSource(ctx, *s); err != nil { + t.Fatalf("Could not ingest source: %v", err) + } + } + for _, a := range test.InLic { + if _, err := b.IngestLicense(ctx, a); err != nil { + t.Fatalf("Could not ingest license: %v", err) + } + } + for i, o := range test.Calls { + cl, err := b.IngestCertifyLegal(ctx, o.PkgSrc, o.Dec, o.Dis, o.Legal) + if (err != nil) != test.ExpIngestErr { + t.Fatalf("did not get expected ingest error, want: %v, got: %v", test.ExpIngestErr, err) + } + if err != nil { + return + } + if (i + 1) == test.IDInFilter { + test.Query.ID = &cl.ID + } + } + got, err := b.CertifyLegal(ctx, test.Query) + if (err != nil) != test.ExpQueryErr { + t.Fatalf("did not get expected query error, want: %v, got: %v", test.ExpQueryErr, err) + } + if err != nil { + return + } + if diff := cmp.Diff(test.ExpLegal, got, ignoreID); diff != "" { + t.Errorf("Unexpected results. (-want +got):\n%s", diff) + } + }) + } +} + +func TestLegals(t *testing.T) { + type call struct { + PkgSrc model.PackageOrSourceInputs + Dec [][]*model.LicenseInputSpec + Dis [][]*model.LicenseInputSpec + Legal []*model.CertifyLegalInputSpec + } + tests := []struct { + Name string + InPkg []*model.PkgInputSpec + InSrc []*model.SourceInputSpec + InLic []*model.LicenseInputSpec + Calls []call + Query *model.CertifyLegalSpec + ExpLegal []*model.CertifyLegal + ExpIngestErr bool + ExpQueryErr bool + }{ + { + Name: "HappyPath", + InPkg: []*model.PkgInputSpec{testdata.P1, testdata.P2}, + InLic: []*model.LicenseInputSpec{testdata.L1}, + Calls: []call{ + { + PkgSrc: model.PackageOrSourceInputs{ + Packages: []*model.PkgInputSpec{testdata.P1, testdata.P2}, + }, + Dec: [][]*model.LicenseInputSpec{{testdata.L1}, {testdata.L1}}, + Dis: [][]*model.LicenseInputSpec{{}, {}}, + Legal: []*model.CertifyLegalInputSpec{ + {Justification: "test justification"}, + {Justification: "test justification"}, + }, + }, + }, + Query: &model.CertifyLegalSpec{ + Justification: ptrfrom.String("test justification"), + }, + ExpLegal: []*model.CertifyLegal{ + { + Subject: testdata.P1out, + DeclaredLicenses: []*model.License{testdata.L1out}, + Justification: "test justification", + }, + { + Subject: testdata.P2out, + DeclaredLicenses: []*model.License{testdata.L1out}, + Justification: "test justification", + }, + }, + }, + } + ignoreID := cmp.FilterPath(func(p cmp.Path) bool { + return strings.Compare(".ID", p[len(p)-1].String()) == 0 + }, cmp.Ignore()) + ctx := context.Background() + arangArg := getArangoConfig() + err := deleteDatabase(ctx, arangArg) + if err != nil { + t.Fatalf("error deleting arango database: %v", err) + } + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + b, err := getBackend(ctx, arangArg) + if err != nil { + t.Fatalf("Could not instantiate testing backend: %v", err) + } + for _, p := range test.InPkg { + if _, err := b.IngestPackage(ctx, *p); err != nil { + t.Fatalf("Could not ingest package: %v", err) + } + } + for _, s := range test.InSrc { + if _, err := b.IngestSource(ctx, *s); err != nil { + t.Fatalf("Could not ingest source: %v", err) + } + } + for _, a := range test.InLic { + if _, err := b.IngestLicense(ctx, a); err != nil { + t.Fatalf("Could not ingest license: %v", err) + } + } + for _, o := range test.Calls { + _, err := b.IngestCertifyLegals(ctx, o.PkgSrc, o.Dec, o.Dis, o.Legal) + if (err != nil) != test.ExpIngestErr { + t.Fatalf("did not get expected ingest error, want: %v, got: %v", test.ExpIngestErr, err) + } + if err != nil { + return + } + } + got, err := b.CertifyLegal(ctx, test.Query) + if (err != nil) != test.ExpQueryErr { + t.Fatalf("did not get expected query error, want: %v, got: %v", test.ExpQueryErr, err) + } + if err != nil { + return + } + if diff := cmp.Diff(test.ExpLegal, got, ignoreID); diff != "" { + t.Errorf("Unexpected results. (-want +got):\n%s", diff) + } + }) + } +} diff --git a/pkg/assembler/backends/arangodb/license.go b/pkg/assembler/backends/arangodb/license.go new file mode 100644 index 0000000000..927c701a37 --- /dev/null +++ b/pkg/assembler/backends/arangodb/license.go @@ -0,0 +1,320 @@ +// +// Copyright 2023 The GUAC Authors. +// +// 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 arangodb + +import ( + "context" + "fmt" + "slices" + "strings" + + "github.com/arangodb/go-driver" + "github.com/guacsec/guac/pkg/assembler/graphql/model" +) + +func (c *arangoClient) Licenses(ctx context.Context, licenseSpec *model.LicenseSpec) ([]*model.License, error) { + values := map[string]any{} + aqb := setLicenseMatchValues(licenseSpec, values) + aqb.query.WriteString("\n") + aqb.query.WriteString(`RETURN { + "id": license._id, + "name": license.name, + "inline": license.inline, + "listversion": license.listversion, +}`) + + cursor, err := executeQueryWithRetry(ctx, c.db, aqb.string(), values, "Licenses") + if err != nil { + return nil, fmt.Errorf("failed to query for license: %w", err) + } + defer cursor.Close() + + return getLicenses(ctx, cursor) +} + +func setLicenseMatchValues(licenseSpec *model.LicenseSpec, queryValues map[string]any) *arangoQueryBuilder { + aqb := newForQuery(licensesStr, "license") + if licenseSpec != nil { + if licenseSpec.ID != nil { + aqb.filter("license", "_id", "==", "@id") + queryValues["id"] = *licenseSpec.ID + } + if licenseSpec.Name != nil { + aqb.filter("license", "name", "==", "@name") + queryValues["name"] = *licenseSpec.Name + } + if licenseSpec.Inline != nil { + aqb.filter("license", "inline", "==", "@inline") + queryValues["inline"] = *licenseSpec.Inline + } + if licenseSpec.ListVersion != nil { + aqb.filter("license", "listversion", "==", "@listversion") + queryValues["listversion"] = *licenseSpec.ListVersion + } + } + return aqb +} + +func getLicenseQueryValues(license *model.LicenseInputSpec) map[string]any { + values := map[string]any{} + values["name"] = license.Name + values["inline"] = nilToEmpty(license.Inline) + values["listversion"] = nilToEmpty(license.ListVersion) + return values +} + +func nilToEmpty(s *string) string { + if s == nil { + return "" + } + return *s +} + +func (c *arangoClient) IngestLicenses(ctx context.Context, licenses []*model.LicenseInputSpec) ([]*model.License, error) { + + var listOfValues []map[string]any + + for i := range licenses { + listOfValues = append(listOfValues, getLicenseQueryValues(licenses[i])) + } + + var documents []string + for _, val := range listOfValues { + bs, _ := json.Marshal(val) + documents = append(documents, string(bs)) + } + + queryValues := map[string]any{} + queryValues["documents"] = fmt.Sprint(strings.Join(documents, ",")) + + var sb strings.Builder + + sb.WriteString("for doc in [") + for i, val := range listOfValues { + bs, _ := json.Marshal(val) + if i == len(listOfValues)-1 { + sb.WriteString(string(bs)) + } else { + sb.WriteString(string(bs) + ",") + } + } + sb.WriteString("]") + + query := ` +UPSERT { name:doc.name, inline:doc.inline, listversion:doc.listversion } +INSERT { name:doc.name, inline:doc.inline, listversion:doc.listversion } +UPDATE {} IN licenses OPTIONS { indexHint: "byNameInlineListVer" } +RETURN { + "id": NEW._id, + "name": NEW.name, + "inline": NEW.inline, + "listversion": NEW.listversion, +}` + + sb.WriteString(query) + + cursor, err := executeQueryWithRetry(ctx, c.db, sb.String(), nil, "IngestLicenses") + if err != nil { + return nil, fmt.Errorf("failed to ingest license: %w", err) + } + defer cursor.Close() + + return getLicenses(ctx, cursor) + +} + +func (c *arangoClient) IngestLicense(ctx context.Context, license *model.LicenseInputSpec) (*model.License, error) { + query := ` +UPSERT { name:@name, inline:@inline, listversion:@listversion } +INSERT { name:@name, inline:@inline, listversion:@listversion } +UPDATE {} IN licenses OPTIONS { indexHint: "byNameInlineListVer" } +RETURN { + "id": NEW._id, + "name": NEW.name, + "inline": NEW.inline, + "listversion": NEW.listversion, +}` + + cursor, err := executeQueryWithRetry(ctx, c.db, query, getLicenseQueryValues(license), "IngestLicense") + if err != nil { + return nil, fmt.Errorf("failed to ingest license: %w", err) + } + defer cursor.Close() + + createdLicenses, err := getLicenses(ctx, cursor) + if err != nil { + return nil, fmt.Errorf("failed to get licenses from arango cursor: %w", err) + } + if len(createdLicenses) == 1 { + return createdLicenses[0], nil + } else { + return nil, fmt.Errorf("number of licenses ingested is greater than one") + } +} + +func getLicenses(ctx context.Context, cursor driver.Cursor) ([]*model.License, error) { + var createdLicenses []*model.License + for { + var doc *model.License + _, err := cursor.ReadDocument(ctx, &doc) + if err != nil { + if driver.IsNoMoreDocuments(err) { + break + } else { + return nil, fmt.Errorf("failed to get license from cursor: %w", err) + } + } else { + if *doc.Inline == "" { + doc.Inline = nil + } + if *doc.ListVersion == "" { + doc.ListVersion = nil + } + createdLicenses = append(createdLicenses, doc) + } + } + return createdLicenses, nil +} + +func (c *arangoClient) getLicenses(ctx context.Context, licenses []*model.LicenseInputSpec) ([]*model.License, error) { + var listOfValues []map[string]any + for i := range licenses { + listOfValues = append(listOfValues, getLicenseQueryValues(licenses[i])) + } + + var documents []string + for _, val := range listOfValues { + bs, _ := json.Marshal(val) + documents = append(documents, string(bs)) + } + + queryValues := map[string]any{} + queryValues["documents"] = fmt.Sprint(strings.Join(documents, ",")) + + var sb strings.Builder + + sb.WriteString("for doc in [") + for i, val := range listOfValues { + bs, _ := json.Marshal(val) + if i == len(listOfValues)-1 { + sb.WriteString(string(bs)) + } else { + sb.WriteString(string(bs) + ",") + } + } + sb.WriteString("]") + sb.WriteString("\n") + + arangoQueryBuilder := newForQuery(licensesStr, "lic") + arangoQueryBuilder.filter("lic", "name", "==", "doc.name") + arangoQueryBuilder.filter("lic", "inline", "==", "doc.inline") + arangoQueryBuilder.filter("lic", "listversion", "==", "doc.listversion") + arangoQueryBuilder.query.WriteString("\n") + arangoQueryBuilder.query.WriteString(`RETURN { + "id": lic._id, + "name": lic.name, + "inline": lic.inline, + "listversion": lic.listversion + }`) + + sb.WriteString(arangoQueryBuilder.string()) + + cursor, err := executeQueryWithRetry(ctx, c.db, sb.String(), nil, "getMaterials") + if err != nil { + return nil, fmt.Errorf("failed to query for Licenses: %w", err) + } + defer cursor.Close() + + return getLicenses(ctx, cursor) +} + +func (c *arangoClient) getLicensesByID(ctx context.Context, licIDs []string) ([]*model.License, error) { + var listOfValues []map[string]any + for _, id := range licIDs { + values := map[string]any{} + values["id"] = id + listOfValues = append(listOfValues, values) + } + + var documents []string + for _, val := range listOfValues { + bs, _ := json.Marshal(val) + documents = append(documents, string(bs)) + } + + queryValues := map[string]any{} + queryValues["documents"] = fmt.Sprint(strings.Join(documents, ",")) + + var sb strings.Builder + + sb.WriteString("for doc in [") + for i, val := range listOfValues { + bs, _ := json.Marshal(val) + if i == len(listOfValues)-1 { + sb.WriteString(string(bs)) + } else { + sb.WriteString(string(bs) + ",") + } + } + sb.WriteString("]") + sb.WriteString("\n") + + arangoQueryBuilder := newForQuery(licensesStr, "lic") + arangoQueryBuilder.filter("lic", "_id", "==", "doc.id") + arangoQueryBuilder.query.WriteString("\n") + arangoQueryBuilder.query.WriteString(`RETURN { + "id": lic._id, + "name": lic.name, + "inline": lic.inline, + "listversion": lic.listversion + }`) + + sb.WriteString(arangoQueryBuilder.string()) + + cursor, err := executeQueryWithRetry(ctx, c.db, sb.String(), nil, "getLicensesByID") + if err != nil { + return nil, fmt.Errorf("failed to query for Licenses: %w", err) + } + defer cursor.Close() + + return getLicenses(ctx, cursor) +} + +func licenseMatch(filters []*model.LicenseSpec, values []*model.License) bool { + left := slices.Clone(values) + + for _, f := range filters { + found := false + for i, v := range left { + if f.ID != nil && *f.ID == v.ID { + found = true + left = slices.Delete(left, i, i+1) + break + } + if (f.Name == nil || *f.Name == v.Name) && + (f.Inline == nil || (v.Inline != nil && *f.Inline == *v.Inline)) && + (f.ListVersion == nil || (v.ListVersion != nil && *f.ListVersion == *v.ListVersion)) { + found = true + left = slices.Delete(left, i, i+1) + break + } + } + if !found { + return false + } + } + return true +} diff --git a/pkg/assembler/backends/arangodb/license_test.go b/pkg/assembler/backends/arangodb/license_test.go new file mode 100644 index 0000000000..c172ed63d4 --- /dev/null +++ b/pkg/assembler/backends/arangodb/license_test.go @@ -0,0 +1,221 @@ +// +// Copyright 2023 The GUAC Authors. +// +// 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. + +//go:build integration + +package arangodb + +import ( + "context" + "slices" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/guacsec/guac/internal/testing/ptrfrom" + "github.com/guacsec/guac/internal/testing/testdata" + "github.com/guacsec/guac/pkg/assembler/graphql/model" +) + +func lessLicense(a, b *model.License) int { + return strings.Compare(a.Name, b.Name) +} + +func Test_Licenses(t *testing.T) { + ctx := context.Background() + arangArg := getArangoConfig() + err := deleteDatabase(ctx, arangArg) + if err != nil { + t.Fatalf("error deleting arango database: %v", err) + } + tests := []struct { + Name string + Ingests []*model.LicenseInputSpec + ExpIngestErr bool + IDInFilter int + Query *model.LicenseSpec + Exp []*model.License + ExpQueryErr bool + }{ + { + Name: "HappyPath", + Ingests: []*model.LicenseInputSpec{testdata.L1}, + Query: &model.LicenseSpec{}, + Exp: []*model.License{testdata.L1out}, + }, + { + Name: "Duplicates", + Ingests: []*model.LicenseInputSpec{testdata.L1, testdata.L1, testdata.L1}, + Query: &model.LicenseSpec{}, + Exp: []*model.License{testdata.L1out}, + }, + { + Name: "Multiple", + Ingests: []*model.LicenseInputSpec{testdata.L1, testdata.L2}, + Query: &model.LicenseSpec{}, + Exp: []*model.License{testdata.L1out, testdata.L2out}, + }, + { + Name: "Query by Name", + Ingests: []*model.LicenseInputSpec{testdata.L1, testdata.L2, testdata.L3, testdata.L4}, + Query: &model.LicenseSpec{ + Name: ptrfrom.String("BSD-3-Clause"), + }, + Exp: []*model.License{testdata.L1out}, + }, + { + Name: "Query by Inline", + Query: &model.LicenseSpec{ + Inline: &testdata.InlineLicense, + }, + Exp: []*model.License{testdata.L4out}, + }, + { + Name: "Query by ListVersion", + Query: &model.LicenseSpec{ + ListVersion: ptrfrom.String("1.23 2020"), + }, + Exp: []*model.License{testdata.L3out}, + }, + { + Name: "Query by ID", + Ingests: []*model.LicenseInputSpec{testdata.L2, testdata.L3, testdata.L4}, + IDInFilter: 2, + Query: &model.LicenseSpec{}, + Exp: []*model.License{testdata.L3out}, + }, + { + Name: "Query None", + Query: &model.LicenseSpec{ + ListVersion: ptrfrom.String("foo"), + }, + Exp: nil, + }, + } + ignoreID := cmp.FilterPath(func(p cmp.Path) bool { + return strings.Compare(".ID", p[len(p)-1].String()) == 0 + }, cmp.Ignore()) + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + c, err := getBackend(ctx, arangArg) + if err != nil { + t.Fatalf("error creating arango backend: %v", err) + } + for i, ingest := range tt.Ingests { + ingestedLicense, err := c.IngestLicense(ctx, ingest) + if (err != nil) != tt.ExpIngestErr { + t.Errorf("demoClient.IngestLicense() error = %v, wantErr %v", err, tt.ExpIngestErr) + return + } + if err != nil { + return + } + if (i + 1) == tt.IDInFilter { + tt.Query.ID = &ingestedLicense.ID + } + } + got, err := c.Licenses(ctx, tt.Query) + if (err != nil) != tt.ExpQueryErr { + t.Errorf("demoClient.Licenses() error = %v, wantErr %v", err, tt.ExpQueryErr) + return + } + if err != nil { + return + } + slices.SortFunc(got, lessLicense) + if diff := cmp.Diff(tt.Exp, got, ignoreID); diff != "" { + t.Errorf("Unexpected results. (-want +got):\n%s", diff) + } + }) + } +} + +func Test_LicensesBulk(t *testing.T) { + ctx := context.Background() + arangArg := getArangoConfig() + err := deleteDatabase(ctx, arangArg) + if err != nil { + t.Fatalf("error deleting arango database: %v", err) + } + tests := []struct { + Name string + Ingests []*model.LicenseInputSpec + ExpIngestErr bool + Query *model.LicenseSpec + Exp []*model.License + ExpQueryErr bool + }{ + { + Name: "Query by Name", + Ingests: []*model.LicenseInputSpec{testdata.L1, testdata.L1, testdata.L2, testdata.L3, testdata.L4}, + Query: &model.LicenseSpec{ + Name: ptrfrom.String("BSD-3-Clause"), + }, + Exp: []*model.License{testdata.L1out}, + }, + { + Name: "Query by Inline", + Query: &model.LicenseSpec{ + Inline: &testdata.InlineLicense, + }, + Exp: []*model.License{testdata.L4out}, + }, + { + Name: "Query by ListVersion", + Query: &model.LicenseSpec{ + ListVersion: ptrfrom.String("1.23 2020"), + }, + Exp: []*model.License{testdata.L3out}, + }, + { + Name: "Query None", + Query: &model.LicenseSpec{ + ListVersion: ptrfrom.String("foo"), + }, + Exp: nil, + }, + } + ignoreID := cmp.FilterPath(func(p cmp.Path) bool { + return strings.Compare(".ID", p[len(p)-1].String()) == 0 + }, cmp.Ignore()) + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + c, err := getBackend(ctx, arangArg) + if err != nil { + t.Fatalf("error creating arango backend: %v", err) + } + _, err = c.IngestLicenses(ctx, tt.Ingests) + if (err != nil) != tt.ExpIngestErr { + t.Errorf("demoClient.IngestLicense() error = %v, wantErr %v", err, tt.ExpIngestErr) + return + } + if err != nil { + return + } + got, err := c.Licenses(ctx, tt.Query) + if (err != nil) != tt.ExpQueryErr { + t.Errorf("demoClient.Licenses() error = %v, wantErr %v", err, tt.ExpQueryErr) + return + } + if err != nil { + return + } + slices.SortFunc(got, lessLicense) + if diff := cmp.Diff(tt.Exp, got, ignoreID); diff != "" { + t.Errorf("Unexpected results. (-want +got):\n%s", diff) + } + }) + } +} diff --git a/pkg/assembler/backends/ent/backend/certifyLegal_test.go b/pkg/assembler/backends/ent/backend/certifyLegal_test.go index b5a3ba9620..f9f7b108cc 100644 --- a/pkg/assembler/backends/ent/backend/certifyLegal_test.go +++ b/pkg/assembler/backends/ent/backend/certifyLegal_test.go @@ -18,16 +18,12 @@ package backend import ( - "time" - "github.com/google/go-cmp/cmp" "github.com/guacsec/guac/internal/testing/ptrfrom" + "github.com/guacsec/guac/internal/testing/testdata" "github.com/guacsec/guac/pkg/assembler/graphql/model" ) -var t2 = time.Unix(1e9, 0) -var t3 = time.Unix(1e9+5, 0) - func (s *Suite) TestLegal() { type call struct { PkgSrc model.PackageOrSourceInput @@ -48,14 +44,14 @@ func (s *Suite) TestLegal() { }{ { Name: "HappyPath", - InPkg: []*model.PkgInputSpec{p1}, - InLic: []*model.LicenseInputSpec{l1}, + InPkg: []*model.PkgInputSpec{testdata.P1}, + InLic: []*model.LicenseInputSpec{testdata.L1}, Calls: []call{ { PkgSrc: model.PackageOrSourceInput{ - Package: p1, + Package: testdata.P1, }, - Dec: []*model.LicenseInputSpec{l1}, + Dec: []*model.LicenseInputSpec{testdata.L1}, Legal: &model.CertifyLegalInputSpec{ Justification: "test justification", }, @@ -66,31 +62,31 @@ func (s *Suite) TestLegal() { }, ExpLegal: []*model.CertifyLegal{ { - Subject: p1out, - DeclaredLicenses: []*model.License{l1out}, + Subject: testdata.P1out, + DeclaredLicenses: []*model.License{testdata.L1out}, Justification: "test justification", }, }, }, { Name: "Ingest same twice", - InPkg: []*model.PkgInputSpec{p1}, - InLic: []*model.LicenseInputSpec{l1}, + InPkg: []*model.PkgInputSpec{testdata.P1}, + InLic: []*model.LicenseInputSpec{testdata.L1}, Calls: []call{ { PkgSrc: model.PackageOrSourceInput{ - Package: p1, + Package: testdata.P1, }, - Dec: []*model.LicenseInputSpec{l1}, + Dec: []*model.LicenseInputSpec{testdata.L1}, Legal: &model.CertifyLegalInputSpec{ Justification: "test justification", }, }, { PkgSrc: model.PackageOrSourceInput{ - Package: p1, + Package: testdata.P1, }, - Dec: []*model.LicenseInputSpec{l1}, + Dec: []*model.LicenseInputSpec{testdata.L1}, Legal: &model.CertifyLegalInputSpec{ Justification: "test justification", }, @@ -101,31 +97,31 @@ func (s *Suite) TestLegal() { }, ExpLegal: []*model.CertifyLegal{ { - Subject: p1out, - DeclaredLicenses: []*model.License{l1out}, + Subject: testdata.P1out, + DeclaredLicenses: []*model.License{testdata.L1out}, Justification: "test justification", }, }, }, { Name: "Query on Justification", - InPkg: []*model.PkgInputSpec{p1}, - InLic: []*model.LicenseInputSpec{l1}, + InPkg: []*model.PkgInputSpec{testdata.P1}, + InLic: []*model.LicenseInputSpec{testdata.L1}, Calls: []call{ { PkgSrc: model.PackageOrSourceInput{ - Package: p1, + Package: testdata.P1, }, - Dec: []*model.LicenseInputSpec{l1}, + Dec: []*model.LicenseInputSpec{testdata.L1}, Legal: &model.CertifyLegalInputSpec{ Justification: "test justification", }, }, { PkgSrc: model.PackageOrSourceInput{ - Package: p1, + Package: testdata.P1, }, - Dec: []*model.LicenseInputSpec{l1}, + Dec: []*model.LicenseInputSpec{testdata.L1}, Legal: &model.CertifyLegalInputSpec{ Justification: "test justification 2", }, @@ -136,31 +132,31 @@ func (s *Suite) TestLegal() { }, ExpLegal: []*model.CertifyLegal{ { - Subject: p1out, - DeclaredLicenses: []*model.License{l1out}, + Subject: testdata.P1out, + DeclaredLicenses: []*model.License{testdata.L1out}, Justification: "test justification 2", }, }, }, { Name: "Query on Package", - InPkg: []*model.PkgInputSpec{p1, p2}, - InLic: []*model.LicenseInputSpec{l1}, + InPkg: []*model.PkgInputSpec{testdata.P1, testdata.P2}, + InLic: []*model.LicenseInputSpec{testdata.L1}, Calls: []call{ { PkgSrc: model.PackageOrSourceInput{ - Package: p1, + Package: testdata.P1, }, - Dec: []*model.LicenseInputSpec{l1}, + Dec: []*model.LicenseInputSpec{testdata.L1}, Legal: &model.CertifyLegalInputSpec{ Justification: "test justification", }, }, { PkgSrc: model.PackageOrSourceInput{ - Package: p2, + Package: testdata.P2, }, - Dec: []*model.LicenseInputSpec{l1}, + Dec: []*model.LicenseInputSpec{testdata.L1}, Legal: &model.CertifyLegalInputSpec{ Justification: "test justification", }, @@ -175,31 +171,31 @@ func (s *Suite) TestLegal() { }, ExpLegal: []*model.CertifyLegal{ { - Subject: p1out, - DeclaredLicenses: []*model.License{l1out}, + Subject: testdata.P1out, + DeclaredLicenses: []*model.License{testdata.L1out}, Justification: "test justification", }, }, }, { Name: "Query on Source", - InSrc: []*model.SourceInputSpec{s1, s2}, - InLic: []*model.LicenseInputSpec{l1}, + InSrc: []*model.SourceInputSpec{testdata.S1, testdata.S2}, + InLic: []*model.LicenseInputSpec{testdata.L1}, Calls: []call{ { PkgSrc: model.PackageOrSourceInput{ - Source: s1, + Source: testdata.S1, }, - Dec: []*model.LicenseInputSpec{l1}, + Dec: []*model.LicenseInputSpec{testdata.L1}, Legal: &model.CertifyLegalInputSpec{ Justification: "test justification", }, }, { PkgSrc: model.PackageOrSourceInput{ - Source: s2, + Source: testdata.S2, }, - Dec: []*model.LicenseInputSpec{l1}, + Dec: []*model.LicenseInputSpec{testdata.L1}, Legal: &model.CertifyLegalInputSpec{ Justification: "test justification", }, @@ -214,31 +210,31 @@ func (s *Suite) TestLegal() { }, ExpLegal: []*model.CertifyLegal{ { - Subject: s1out, - DeclaredLicenses: []*model.License{l1out}, + Subject: testdata.S1out, + DeclaredLicenses: []*model.License{testdata.L1out}, Justification: "test justification", }, }, }, { Name: "Query on License", - InSrc: []*model.SourceInputSpec{s1}, - InLic: []*model.LicenseInputSpec{l1, l2, l3}, + InSrc: []*model.SourceInputSpec{testdata.S1}, + InLic: []*model.LicenseInputSpec{testdata.L1, testdata.L2, testdata.L3}, Calls: []call{ { PkgSrc: model.PackageOrSourceInput{ - Source: s1, + Source: testdata.S1, }, - Dec: []*model.LicenseInputSpec{l1, l2}, + Dec: []*model.LicenseInputSpec{testdata.L1, testdata.L2}, Legal: &model.CertifyLegalInputSpec{ Justification: "test justification", }, }, { PkgSrc: model.PackageOrSourceInput{ - Source: s1, + Source: testdata.S1, }, - Dec: []*model.LicenseInputSpec{l3}, + Dec: []*model.LicenseInputSpec{testdata.L3}, Legal: &model.CertifyLegalInputSpec{ Justification: "test justification", }, @@ -251,31 +247,31 @@ func (s *Suite) TestLegal() { }, ExpLegal: []*model.CertifyLegal{ { - Subject: s1out, - DeclaredLicenses: []*model.License{l1out, l2out}, + Subject: testdata.S1out, + DeclaredLicenses: []*model.License{testdata.L1out, testdata.L2out}, Justification: "test justification", }, }, }, { Name: "Query on License inline", - InSrc: []*model.SourceInputSpec{s1}, - InLic: []*model.LicenseInputSpec{l1, l2, l4}, + InSrc: []*model.SourceInputSpec{testdata.S1}, + InLic: []*model.LicenseInputSpec{testdata.L1, testdata.L2, testdata.L4}, Calls: []call{ { PkgSrc: model.PackageOrSourceInput{ - Source: s1, + Source: testdata.S1, }, - Dec: []*model.LicenseInputSpec{l1, l4}, + Dec: []*model.LicenseInputSpec{testdata.L1, testdata.L4}, Legal: &model.CertifyLegalInputSpec{ Justification: "test justification", }, }, { PkgSrc: model.PackageOrSourceInput{ - Source: s1, + Source: testdata.S1, }, - Dec: []*model.LicenseInputSpec{l2}, + Dec: []*model.LicenseInputSpec{testdata.L2}, Legal: &model.CertifyLegalInputSpec{ Justification: "test justification", }, @@ -283,24 +279,24 @@ func (s *Suite) TestLegal() { }, Query: &model.CertifyLegalSpec{ DeclaredLicenses: []*model.LicenseSpec{ - {Inline: &inlineLicense}, + {Inline: &testdata.InlineLicense}, }, }, ExpLegal: []*model.CertifyLegal{ { - Subject: s1out, - DeclaredLicenses: []*model.License{l1out, l4out}, + Subject: testdata.S1out, + DeclaredLicenses: []*model.License{testdata.L1out, testdata.L4out}, Justification: "test justification", }, }, }, { Name: "Query on expression", - InPkg: []*model.PkgInputSpec{p1}, + InPkg: []*model.PkgInputSpec{testdata.P1}, Calls: []call{ { PkgSrc: model.PackageOrSourceInput{ - Package: p1, + Package: testdata.P1, }, Legal: &model.CertifyLegalInputSpec{ DeclaredLicense: "GPL OR MIT", @@ -308,7 +304,7 @@ func (s *Suite) TestLegal() { }, { PkgSrc: model.PackageOrSourceInput{ - Package: p1, + Package: testdata.P1, }, Legal: &model.CertifyLegalInputSpec{ DeclaredLicense: "GPL AND MIT", @@ -320,18 +316,18 @@ func (s *Suite) TestLegal() { }, ExpLegal: []*model.CertifyLegal{ { - Subject: p1out, + Subject: testdata.P1out, DeclaredLicense: "GPL AND MIT", }, }, }, { Name: "Query on attribution", - InPkg: []*model.PkgInputSpec{p1}, + InPkg: []*model.PkgInputSpec{testdata.P1}, Calls: []call{ { PkgSrc: model.PackageOrSourceInput{ - Package: p1, + Package: testdata.P1, }, Legal: &model.CertifyLegalInputSpec{ Attribution: "Copyright Jeff", @@ -339,7 +335,7 @@ func (s *Suite) TestLegal() { }, { PkgSrc: model.PackageOrSourceInput{ - Package: p1, + Package: testdata.P1, }, Legal: &model.CertifyLegalInputSpec{ Attribution: "Copyright Bob", @@ -351,70 +347,70 @@ func (s *Suite) TestLegal() { }, ExpLegal: []*model.CertifyLegal{ { - Subject: p1out, + Subject: testdata.P1out, Attribution: "Copyright Jeff", }, }, }, { Name: "Query on time", - InPkg: []*model.PkgInputSpec{p1}, + InPkg: []*model.PkgInputSpec{testdata.P1}, Calls: []call{ { PkgSrc: model.PackageOrSourceInput{ - Package: p1, + Package: testdata.P1, }, Legal: &model.CertifyLegalInputSpec{ - TimeScanned: t3, + TimeScanned: testdata.T3, }, }, { PkgSrc: model.PackageOrSourceInput{ - Package: p1, + Package: testdata.P1, }, Legal: &model.CertifyLegalInputSpec{ - TimeScanned: t2, + TimeScanned: testdata.T2, }, }, }, Query: &model.CertifyLegalSpec{ - TimeScanned: &t2, + TimeScanned: &testdata.T2, }, ExpLegal: []*model.CertifyLegal{ { - Subject: p1out, - TimeScanned: t2, + Subject: testdata.P1out, + TimeScanned: testdata.T2, }, }, }, { Name: "Query multiple", - InPkg: []*model.PkgInputSpec{p1, p2, p3}, - InLic: []*model.LicenseInputSpec{l1}, + InPkg: []*model.PkgInputSpec{testdata.P1, testdata.P2, testdata.P3}, + InLic: []*model.LicenseInputSpec{testdata.L1}, Calls: []call{ { PkgSrc: model.PackageOrSourceInput{ - Package: p1, + Package: testdata.P1, }, - Dec: []*model.LicenseInputSpec{l1}, + Dec: []*model.LicenseInputSpec{testdata.L1}, Legal: &model.CertifyLegalInputSpec{ Justification: "test justification", }, }, { PkgSrc: model.PackageOrSourceInput{ - Package: p2, + Package: testdata.P2, }, - Dec: []*model.LicenseInputSpec{l1}, + Dec: []*model.LicenseInputSpec{testdata.L1}, Legal: &model.CertifyLegalInputSpec{ Justification: "test justification", }, }, { PkgSrc: model.PackageOrSourceInput{ - Package: p3, + Package: testdata.P3, }, - Dec: []*model.LicenseInputSpec{l1}, + Dec: []*model.LicenseInputSpec{testdata.L1}, Legal: &model.CertifyLegalInputSpec{ Justification: "test justification other", }, @@ -425,13 +421,13 @@ func (s *Suite) TestLegal() { }, ExpLegal: []*model.CertifyLegal{ { - Subject: p1out, - DeclaredLicenses: []*model.License{l1out}, + Subject: testdata.P1out, + DeclaredLicenses: []*model.License{testdata.L1out}, Justification: "test justification", }, { - Subject: p2out, - DeclaredLicenses: []*model.License{l1out}, + Subject: testdata.P2out, + DeclaredLicenses: []*model.License{testdata.L1out}, Justification: "test justification", }, }, @@ -441,7 +437,7 @@ func (s *Suite) TestLegal() { Calls: []call{ { PkgSrc: model.PackageOrSourceInput{ - Package: p1, + Package: testdata.P1, }, Legal: &model.CertifyLegalInputSpec{}, }, @@ -453,7 +449,7 @@ func (s *Suite) TestLegal() { Calls: []call{ { PkgSrc: model.PackageOrSourceInput{ - Source: s1, + Source: testdata.S1, }, Legal: &model.CertifyLegalInputSpec{}, }, @@ -462,13 +458,13 @@ func (s *Suite) TestLegal() { }, { Name: "Ingest without License", - InPkg: []*model.PkgInputSpec{p1}, + InPkg: []*model.PkgInputSpec{testdata.P1}, Calls: []call{ { PkgSrc: model.PackageOrSourceInput{ - Package: p1, + Package: testdata.P1, }, - Dec: []*model.LicenseInputSpec{l1}, + Dec: []*model.LicenseInputSpec{testdata.L1}, Legal: &model.CertifyLegalInputSpec{}, }, }, @@ -541,14 +537,14 @@ func (s *Suite) TestLegals() { }{ { Name: "HappyPath", - InPkg: []*model.PkgInputSpec{p1, p2}, - InLic: []*model.LicenseInputSpec{l1}, + InPkg: []*model.PkgInputSpec{testdata.P1, testdata.P2}, + InLic: []*model.LicenseInputSpec{testdata.L1}, Calls: []call{ { PkgSrc: model.PackageOrSourceInputs{ - Packages: []*model.PkgInputSpec{p1, p2}, + Packages: []*model.PkgInputSpec{testdata.P1, testdata.P2}, }, - Dec: [][]*model.LicenseInputSpec{{l1}, {l1}}, + Dec: [][]*model.LicenseInputSpec{{testdata.L1}, {testdata.L1}}, Dis: [][]*model.LicenseInputSpec{{}, {}}, Legal: []*model.CertifyLegalInputSpec{ {Justification: "test justification"}, @@ -561,13 +557,13 @@ func (s *Suite) TestLegals() { }, ExpLegal: []*model.CertifyLegal{ { - Subject: p1out, - DeclaredLicenses: []*model.License{l1out}, + Subject: testdata.P1out, + DeclaredLicenses: []*model.License{testdata.L1out}, Justification: "test justification", }, { - Subject: p2out, - DeclaredLicenses: []*model.License{l1out}, + Subject: testdata.P2out, + DeclaredLicenses: []*model.License{testdata.L1out}, Justification: "test justification", }, }, diff --git a/pkg/assembler/backends/ent/backend/license_test.go b/pkg/assembler/backends/ent/backend/license_test.go index 16fd2aadb3..bbd79aac9a 100644 --- a/pkg/assembler/backends/ent/backend/license_test.go +++ b/pkg/assembler/backends/ent/backend/license_test.go @@ -24,57 +24,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/guacsec/guac/internal/testing/ptrfrom" + "github.com/guacsec/guac/internal/testing/testdata" "github.com/guacsec/guac/pkg/assembler/graphql/model" ) -var l1 = &model.LicenseInputSpec{ - Name: "BSD-3-Clause", - ListVersion: ptrfrom.String("3.21 2023-06-18"), -} - -var l1out = &model.License{ - Name: "BSD-3-Clause", - ListVersion: ptrfrom.String("3.21 2023-06-18"), -} - -var l2 = &model.LicenseInputSpec{ - Name: "GPL-2.0-or-later", - ListVersion: ptrfrom.String("3.21 2023-06-18"), -} - -var l2out = &model.License{ - Name: "GPL-2.0-or-later", - ListVersion: ptrfrom.String("3.21 2023-06-18"), -} - -var l3 = &model.LicenseInputSpec{ - Name: "MPL-2.0", - ListVersion: ptrfrom.String("1.23 2020"), -} - -var l3out = &model.License{ - Name: "MPL-2.0", - ListVersion: ptrfrom.String("1.23 2020"), -} - -var inlineLicense = ` -Redistribution and use of the MAME code or any derivative works are permitted provided that the following conditions are met: -* Redistributions may not be sold, nor may they be used in a commercial product or activity. -* Redistributions that are modified from the original source must include the complete source code, including the source code for all components used by a binary built from the modified sources. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. -* Redistributions must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -` - -var l4 = &model.LicenseInputSpec{ - Name: "LicenseRef-d58b4101", - Inline: &inlineLicense, -} - -var l4out = &model.License{ - Name: "LicenseRef-d58b4101", - Inline: &inlineLicense, -} - func (s *Suite) TestLicense() { tests := []struct { Name string @@ -86,57 +39,57 @@ func (s *Suite) TestLicense() { }{ { Name: "HappyPath", - Ingests: []*model.LicenseInputSpec{l1}, + Ingests: []*model.LicenseInputSpec{testdata.L1}, Query: &model.LicenseSpec{}, - Exp: []*model.License{l1out}, + Exp: []*model.License{testdata.L1out}, }, { Name: "Multiple", - Ingests: []*model.LicenseInputSpec{l1, l2}, + Ingests: []*model.LicenseInputSpec{testdata.L1, testdata.L2}, Query: &model.LicenseSpec{}, - Exp: []*model.License{l1out, l2out}, + Exp: []*model.License{testdata.L1out, testdata.L2out}, }, { Name: "Duplicates", - Ingests: []*model.LicenseInputSpec{l1, l1, l1}, + Ingests: []*model.LicenseInputSpec{testdata.L1, testdata.L1, testdata.L1}, Query: &model.LicenseSpec{}, - Exp: []*model.License{l1out}, + Exp: []*model.License{testdata.L1out}, }, { Name: "Query by Name", - Ingests: []*model.LicenseInputSpec{l1, l2, l3}, + Ingests: []*model.LicenseInputSpec{testdata.L1, testdata.L2, testdata.L3}, Query: &model.LicenseSpec{ Name: ptrfrom.String("BSD-3-Clause"), }, - Exp: []*model.License{l1out}, + Exp: []*model.License{testdata.L1out}, }, { Name: "Query by Inline", - Ingests: []*model.LicenseInputSpec{l2, l3, l4}, + Ingests: []*model.LicenseInputSpec{testdata.L2, testdata.L3, testdata.L4}, Query: &model.LicenseSpec{ - Inline: &inlineLicense, + Inline: &testdata.InlineLicense, }, - Exp: []*model.License{l4out}, + Exp: []*model.License{testdata.L4out}, }, { Name: "Query by ListVersion", - Ingests: []*model.LicenseInputSpec{l2, l3, l4}, + Ingests: []*model.LicenseInputSpec{testdata.L2, testdata.L3, testdata.L4}, Query: &model.LicenseSpec{ ListVersion: ptrfrom.String("1.23 2020"), }, - Exp: []*model.License{l3out}, + Exp: []*model.License{testdata.L3out}, }, { Name: "Query by ID", - Ingests: []*model.LicenseInputSpec{l2, l3, l4}, + Ingests: []*model.LicenseInputSpec{testdata.L2, testdata.L3, testdata.L4}, Query: &model.LicenseSpec{ ID: ptrfrom.String("1"), }, - Exp: []*model.License{l3out}, + Exp: []*model.License{testdata.L3out}, }, { Name: "Query None", - Ingests: []*model.LicenseInputSpec{l2, l3, l4}, + Ingests: []*model.LicenseInputSpec{testdata.L2, testdata.L3, testdata.L4}, Query: &model.LicenseSpec{ ListVersion: ptrfrom.String("foo"), }, @@ -144,7 +97,7 @@ func (s *Suite) TestLicense() { }, { Name: "Query invalid ID", - Ingests: []*model.LicenseInputSpec{l1, l2, l3}, + Ingests: []*model.LicenseInputSpec{testdata.L1, testdata.L2, testdata.L3}, Query: &model.LicenseSpec{ ID: ptrfrom.String("asdf"), }, @@ -210,7 +163,7 @@ func (s *Suite) TestIngestLicenses() { }{ { name: "Multiple", - ingests: []*model.LicenseInputSpec{l1, l2, l3, l4}, + ingests: []*model.LicenseInputSpec{testdata.L1, testdata.L2, testdata.L3, testdata.L4}, exp: []*model.License{{}, {}, {}, {}}, }, }