mirror of https://github.com/docker/docs.git
Add ConsistentInfo and snapshot/timestamp retroactive checksum tests for the builder.
Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
parent
c9e91446a1
commit
5d1b4bc6ec
|
@ -199,8 +199,11 @@ func (rb *repoBuilder) GetConsistentInfo(roleName string) ConsistentInfo {
|
||||||
info.fileMeta = rb.repo.Timestamp.Signed.Meta[roleName]
|
info.fileMeta = rb.repo.Timestamp.Signed.Meta[roleName]
|
||||||
}
|
}
|
||||||
case data.CanonicalRootRole:
|
case data.CanonicalRootRole:
|
||||||
if rb.bootstrappedRootChecksum != nil {
|
switch {
|
||||||
|
case rb.bootstrappedRootChecksum != nil:
|
||||||
info.fileMeta = *rb.bootstrappedRootChecksum
|
info.fileMeta = *rb.bootstrappedRootChecksum
|
||||||
|
case rb.repo.Snapshot != nil:
|
||||||
|
info.fileMeta = rb.repo.Snapshot.Signed.Meta[roleName]
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if rb.repo.Snapshot != nil {
|
if rb.repo.Snapshot != nil {
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
package tuf_test
|
package tuf_test
|
||||||
|
|
||||||
// package tuf_test to avoid an import cycle since we are using testutils.EmptyRepo
|
// tests for builder that live in an external package, tuf_test, so that we can use
|
||||||
|
// the testutils without causing an import cycle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/notary"
|
||||||
"github.com/docker/notary/trustpinning"
|
"github.com/docker/notary/trustpinning"
|
||||||
"github.com/docker/notary/tuf"
|
"github.com/docker/notary/tuf"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
"github.com/docker/notary/tuf/signed"
|
"github.com/docker/notary/tuf/signed"
|
||||||
"github.com/docker/notary/tuf/testutils"
|
"github.com/docker/notary/tuf/testutils"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -344,3 +351,325 @@ func TestGenerateTimestampInvalidOperations(t *testing.T) {
|
||||||
require.IsType(t, data.ErrInvalidMetadata{}, err)
|
require.IsType(t, data.ErrInvalidMetadata{}, err)
|
||||||
require.False(t, builder.IsLoaded(data.CanonicalTimestampRole))
|
require.False(t, builder.IsLoaded(data.CanonicalTimestampRole))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetConsistentInfo(t *testing.T) {
|
||||||
|
gun := "docker.com/notary"
|
||||||
|
repo, _, err := testutils.EmptyRepo(gun)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// add some hashes for items in the snapshot that don't correspond to real metadata, but that
|
||||||
|
// will cause ConsistentInfo to behave differently
|
||||||
|
realSha512Sum := sha512.Sum512([]byte("stuff"))
|
||||||
|
repo.Snapshot.Signed.Meta["only512"] = data.FileMeta{Hashes: data.Hashes{notary.SHA512: realSha512Sum[:]}}
|
||||||
|
repo.Snapshot.Signed.Meta["targets/random"] = data.FileMeta{Hashes: data.Hashes{"randomsha": []byte("12345")}}
|
||||||
|
repo.Snapshot.Signed.Meta["targets/nohashes"] = data.FileMeta{Length: 1}
|
||||||
|
|
||||||
|
extraMeta := []string{"only512", "targets/random", "targets/nohashes"}
|
||||||
|
|
||||||
|
meta, err := testutils.SignAndSerialize(repo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
builder := tuf.NewRepoBuilder(nil, gun, nil, trustpinning.TrustPinConfig{})
|
||||||
|
// if neither snapshot nor timestamp are loaded, no matter how much other data is loaded, consistent info
|
||||||
|
// is empty except for timestamp: timestamps have no checksums, and the length is always -1
|
||||||
|
for _, roleToLoad := range []string{data.CanonicalRootRole, data.CanonicalTargetsRole} {
|
||||||
|
require.NoError(t, builder.Load(roleToLoad, meta[roleToLoad], 0, false))
|
||||||
|
for _, checkName := range append(data.BaseRoles, extraMeta...) {
|
||||||
|
ci := builder.GetConsistentInfo(checkName)
|
||||||
|
require.Equal(t, checkName, ci.ConsistentName())
|
||||||
|
|
||||||
|
switch checkName {
|
||||||
|
case data.CanonicalTimestampRole:
|
||||||
|
// timestamp's size is always the max timestamp size
|
||||||
|
require.True(t, ci.ChecksumKnown())
|
||||||
|
require.Equal(t, notary.MaxTimestampSize, ci.Length())
|
||||||
|
default:
|
||||||
|
require.False(t, ci.ChecksumKnown())
|
||||||
|
require.Equal(t, int64(-1), ci.Length())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// once timestamp is loaded, we can get the consistent info for snapshot but nothing else
|
||||||
|
require.NoError(t, builder.Load(data.CanonicalTimestampRole, meta[data.CanonicalTimestampRole], 0, false))
|
||||||
|
for _, checkName := range append(data.BaseRoles, extraMeta...) {
|
||||||
|
ci := builder.GetConsistentInfo(checkName)
|
||||||
|
|
||||||
|
switch checkName {
|
||||||
|
case data.CanonicalSnapshotRole:
|
||||||
|
cName := utils.ConsistentName(data.CanonicalSnapshotRole,
|
||||||
|
repo.Timestamp.Signed.Meta[data.CanonicalSnapshotRole].Hashes[notary.SHA256])
|
||||||
|
require.Equal(t, cName, ci.ConsistentName())
|
||||||
|
require.True(t, ci.ChecksumKnown())
|
||||||
|
require.True(t, ci.Length() > -1)
|
||||||
|
case data.CanonicalTimestampRole:
|
||||||
|
// timestamp's canonical name is always "timestamp" and its size is always the max
|
||||||
|
// timestamp size
|
||||||
|
require.Equal(t, data.CanonicalTimestampRole, ci.ConsistentName())
|
||||||
|
require.True(t, ci.ChecksumKnown())
|
||||||
|
require.Equal(t, notary.MaxTimestampSize, ci.Length())
|
||||||
|
default:
|
||||||
|
require.Equal(t, checkName, ci.ConsistentName())
|
||||||
|
require.False(t, ci.ChecksumKnown())
|
||||||
|
require.Equal(t, int64(-1), ci.Length())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// once the snapshot is loaded, we can get real consistent info for all loaded roles
|
||||||
|
require.NoError(t, builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 0, false))
|
||||||
|
for _, checkName := range data.BaseRoles {
|
||||||
|
ci := builder.GetConsistentInfo(checkName)
|
||||||
|
require.True(t, ci.ChecksumKnown(), "%s's checksum is not known", checkName)
|
||||||
|
|
||||||
|
switch checkName {
|
||||||
|
case data.CanonicalTimestampRole:
|
||||||
|
// timestamp's canonical name is always "timestamp" and its size is always -1
|
||||||
|
require.Equal(t, data.CanonicalTimestampRole, ci.ConsistentName())
|
||||||
|
require.Equal(t, notary.MaxTimestampSize, ci.Length())
|
||||||
|
default:
|
||||||
|
fileInfo := repo.Snapshot.Signed.Meta
|
||||||
|
if checkName == data.CanonicalSnapshotRole {
|
||||||
|
fileInfo = repo.Timestamp.Signed.Meta
|
||||||
|
}
|
||||||
|
|
||||||
|
cName := utils.ConsistentName(checkName, fileInfo[checkName].Hashes[notary.SHA256])
|
||||||
|
require.Equal(t, cName, ci.ConsistentName())
|
||||||
|
require.True(t, ci.Length() > -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the fake roles have invalid-ish checksums: the ConsistentInfos for those will return
|
||||||
|
// non-consistent names but non -1 sizes
|
||||||
|
for _, checkName := range extraMeta {
|
||||||
|
ci := builder.GetConsistentInfo(checkName)
|
||||||
|
require.Equal(t, checkName, ci.ConsistentName()) // because no sha256 hash
|
||||||
|
require.True(t, ci.ChecksumKnown())
|
||||||
|
require.True(t, ci.Length() > -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// a non-existent role's ConsistentInfo is empty
|
||||||
|
ci := builder.GetConsistentInfo("nonExistent")
|
||||||
|
require.Equal(t, "nonExistent", ci.ConsistentName())
|
||||||
|
require.False(t, ci.ChecksumKnown())
|
||||||
|
require.Equal(t, int64(-1), ci.Length())
|
||||||
|
|
||||||
|
// when we bootstrap a new builder, the root has consistent info because the checksum is provided,
|
||||||
|
// but nothing else does
|
||||||
|
builder = builder.BootstrapNewBuilder()
|
||||||
|
for _, checkName := range append(data.BaseRoles, extraMeta...) {
|
||||||
|
ci := builder.GetConsistentInfo(checkName)
|
||||||
|
|
||||||
|
switch checkName {
|
||||||
|
case data.CanonicalTimestampRole:
|
||||||
|
// timestamp's size is always the max timestamp size
|
||||||
|
require.Equal(t, checkName, ci.ConsistentName())
|
||||||
|
require.True(t, ci.ChecksumKnown())
|
||||||
|
require.Equal(t, notary.MaxTimestampSize, ci.Length())
|
||||||
|
|
||||||
|
case data.CanonicalRootRole:
|
||||||
|
cName := utils.ConsistentName(data.CanonicalRootRole,
|
||||||
|
repo.Snapshot.Signed.Meta[data.CanonicalRootRole].Hashes[notary.SHA256])
|
||||||
|
|
||||||
|
require.Equal(t, cName, ci.ConsistentName())
|
||||||
|
require.True(t, ci.ChecksumKnown())
|
||||||
|
require.True(t, ci.Length() > -1)
|
||||||
|
|
||||||
|
default:
|
||||||
|
require.Equal(t, checkName, ci.ConsistentName())
|
||||||
|
require.False(t, ci.ChecksumKnown())
|
||||||
|
require.Equal(t, int64(-1), ci.Length())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No matter what order timestamp and snapshot is loaded, if the snapshot's checksum doesn't match
|
||||||
|
// what's in the timestamp, the builder will error and refuse to load the latest piece of metadata
|
||||||
|
// whether that is snapshot (because it was loaded after timestamp) or timestamp (because builder
|
||||||
|
// retroactive checks the loaded snapshot's checksum). Timestamp ONLY checks the snapshot checksum.
|
||||||
|
func TestTimestampPreAndPostChecksumming(t *testing.T) {
|
||||||
|
gun := "docker.com/notary"
|
||||||
|
repo, _, err := testutils.EmptyRepo(gun, "targets/other", "targets/other/other")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// add invalid checkums for all the other roles to timestamp too, and show that
|
||||||
|
// cached items aren't checksummed against this
|
||||||
|
fakeChecksum, err := data.NewFileMeta(bytes.NewBuffer([]byte("fake")), notary.SHA256, notary.SHA512)
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, roleName := range append(data.BaseRoles, "targets/other") {
|
||||||
|
// add a wrong checksum for every role, including timestamp itself
|
||||||
|
repo.Timestamp.Signed.Meta[roleName] = fakeChecksum
|
||||||
|
}
|
||||||
|
// this will overwrite the snapshot checksum with the right one
|
||||||
|
meta, err := testutils.SignAndSerialize(repo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
// ensure that the fake meta for other roles weren't destroyed by signing the timestamp
|
||||||
|
require.Len(t, repo.Timestamp.Signed.Meta, 5)
|
||||||
|
|
||||||
|
snapJSON := append(meta[data.CanonicalSnapshotRole], ' ')
|
||||||
|
|
||||||
|
// --- load timestamp first
|
||||||
|
builder := tuf.NewRepoBuilder(nil, gun, nil, trustpinning.TrustPinConfig{})
|
||||||
|
require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false))
|
||||||
|
// timestamp doesn't fail, even though its checksum for root is wrong according to timestamp
|
||||||
|
require.NoError(t, builder.Load(data.CanonicalTimestampRole, meta[data.CanonicalTimestampRole], 0, false))
|
||||||
|
// loading the snapshot in fails, because of the checksum the timestamp has
|
||||||
|
err = builder.Load(data.CanonicalSnapshotRole, snapJSON, 0, false)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.IsType(t, data.ErrMismatchedChecksum{}, err)
|
||||||
|
require.True(t, builder.IsLoaded(data.CanonicalTimestampRole))
|
||||||
|
require.False(t, builder.IsLoaded(data.CanonicalSnapshotRole))
|
||||||
|
// all the other metadata can be loaded in, even though the checksums are wrong according to timestamp
|
||||||
|
for _, roleName := range []string{data.CanonicalTargetsRole, "targets/other"} {
|
||||||
|
require.NoError(t, builder.Load(roleName, meta[roleName], 0, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- load snapshot first
|
||||||
|
builder = tuf.NewRepoBuilder(nil, gun, nil, trustpinning.TrustPinConfig{})
|
||||||
|
for _, roleName := range append(data.BaseRoles, "targets/other") {
|
||||||
|
switch roleName {
|
||||||
|
case data.CanonicalTimestampRole:
|
||||||
|
continue
|
||||||
|
case data.CanonicalSnapshotRole:
|
||||||
|
require.NoError(t, builder.Load(roleName, snapJSON, 0, false))
|
||||||
|
default:
|
||||||
|
require.NoError(t, builder.Load(roleName, meta[roleName], 0, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// timestamp fails because the snapshot checksum is wrong
|
||||||
|
err = builder.Load(data.CanonicalTimestampRole, meta[data.CanonicalTimestampRole], 0, false)
|
||||||
|
require.Error(t, err)
|
||||||
|
checksumErr, ok := err.(data.ErrMismatchedChecksum)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Contains(t, checksumErr.Error(), "checksum for snapshot did not match")
|
||||||
|
require.False(t, builder.IsLoaded(data.CanonicalTimestampRole))
|
||||||
|
require.True(t, builder.IsLoaded(data.CanonicalSnapshotRole))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates metadata in the following manner:
|
||||||
|
// - the snapshot has bad checksums for itself and for timestamp, to show that those aren't checked
|
||||||
|
// - snapshot has valid checksums for root, targets, and targets/other
|
||||||
|
// - snapshot doesn't have a checksum for targets/other/other, but targets/other/other is a valid
|
||||||
|
// delegation role in targets/other and there is metadata for targets/other/other that is correctly
|
||||||
|
// signed
|
||||||
|
func setupSnapshotChecksumming(t *testing.T, gun string) map[string][]byte {
|
||||||
|
repo, _, err := testutils.EmptyRepo(gun, "targets/other", "targets/other/other")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// add invalid checkums for all the other roles to timestamp too, and show that
|
||||||
|
// cached items aren't checksummed against this
|
||||||
|
fakeChecksum, err := data.NewFileMeta(bytes.NewBuffer([]byte("fake")), notary.SHA256, notary.SHA512)
|
||||||
|
require.NoError(t, err)
|
||||||
|
// fake the snapshot and timestamp checksums
|
||||||
|
repo.Snapshot.Signed.Meta[data.CanonicalSnapshotRole] = fakeChecksum
|
||||||
|
repo.Snapshot.Signed.Meta[data.CanonicalTimestampRole] = fakeChecksum
|
||||||
|
|
||||||
|
meta, err := testutils.SignAndSerialize(repo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
// ensure that the fake metadata for other roles wasn't destroyed by signing
|
||||||
|
require.Len(t, repo.Snapshot.Signed.Meta, 5)
|
||||||
|
|
||||||
|
// create delegation metadata that should not be in snapshot, but has a valid role and signature
|
||||||
|
_, err = repo.InitTargets("targets/other/other")
|
||||||
|
require.NoError(t, err)
|
||||||
|
s, err := repo.SignTargets("targets/other/other", data.DefaultExpires(data.CanonicalTargetsRole))
|
||||||
|
require.NoError(t, err)
|
||||||
|
meta["targets/other/other"], err = json.Marshal(s)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return meta
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the snapshot is loaded first (-ish, because really root has to be loaded first)
|
||||||
|
// it will be used to validate the checksums of all other metadata that gets loaded.
|
||||||
|
// If the checksum doesn't match, or if there is no checksum, then the other metadata
|
||||||
|
// cannot be loaded.
|
||||||
|
func TestSnapshotLoadedFirstChecksumsOthers(t *testing.T) {
|
||||||
|
gun := "docker.com/notary"
|
||||||
|
meta := setupSnapshotChecksumming(t, gun)
|
||||||
|
// --- load root then snapshot
|
||||||
|
builder := tuf.NewRepoBuilder(nil, gun, nil, trustpinning.TrustPinConfig{})
|
||||||
|
require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false))
|
||||||
|
require.NoError(t, builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 0, false))
|
||||||
|
|
||||||
|
// loading timestamp is fine, even though the timestamp metadata has the wrong checksum because
|
||||||
|
// we don't check timestamp checksums
|
||||||
|
require.NoError(t, builder.Load(data.CanonicalTimestampRole, meta[data.CanonicalTimestampRole], 0, false))
|
||||||
|
|
||||||
|
// loading the other roles' metadata with a space will fail because of a checksum failure (builder
|
||||||
|
// checks right away if the snapshot is loaded) - in the case of targets/other/other, which should
|
||||||
|
// not be in snapshot at all, loading should fail even without a space because there is no checksum
|
||||||
|
// for it
|
||||||
|
for _, roleNameToLoad := range []string{data.CanonicalTargetsRole, "targets/other"} {
|
||||||
|
err := builder.Load(roleNameToLoad, append(meta[roleNameToLoad], ' '), 0, false)
|
||||||
|
require.Error(t, err)
|
||||||
|
checksumErr, ok := err.(data.ErrMismatchedChecksum)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Contains(t, checksumErr.Error(), fmt.Sprintf("checksum for %s did not match", roleNameToLoad))
|
||||||
|
require.False(t, builder.IsLoaded(roleNameToLoad))
|
||||||
|
|
||||||
|
// now load it for real (since we need targets loaded before trying to load "targets/other")
|
||||||
|
require.NoError(t, builder.Load(roleNameToLoad, meta[roleNameToLoad], 0, false))
|
||||||
|
}
|
||||||
|
// loading the non-existent role wil fail
|
||||||
|
err := builder.Load("targets/other/other", meta["targets/other/other"], 0, false)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.IsType(t, data.ErrMissingMeta{}, err)
|
||||||
|
require.False(t, builder.IsLoaded("targets/other/other"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any other metadata is loaded first, when the snapshot is loaded it will retroactively go back
|
||||||
|
// and validate that metadata. If anything fails to validate, or there is metadata for which this
|
||||||
|
// snapshot has no checksums for, the snapshot will fail to validate.
|
||||||
|
func TestSnapshotLoadedAfterChecksumsOthersRetroactively(t *testing.T) {
|
||||||
|
gun := "docker.com/notary"
|
||||||
|
meta := setupSnapshotChecksumming(t, gun)
|
||||||
|
|
||||||
|
// --- load all the other metadata first, but with an extra space at the end which should
|
||||||
|
// --- validate fine, except for the checksum.
|
||||||
|
for _, roleNameToPermute := range append(data.BaseRoles, "targets/other") {
|
||||||
|
builder := tuf.NewRepoBuilder(nil, gun, nil, trustpinning.TrustPinConfig{})
|
||||||
|
if roleNameToPermute == data.CanonicalSnapshotRole {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// load all the roles normally, except for roleToPermute, which has one space added
|
||||||
|
// to the end, thus changing the checksum
|
||||||
|
for _, roleNameToLoad := range append(data.BaseRoles, "targets/other") {
|
||||||
|
switch roleNameToLoad {
|
||||||
|
case data.CanonicalSnapshotRole:
|
||||||
|
continue // we load this later
|
||||||
|
case roleNameToPermute:
|
||||||
|
// having a space added at the end should not affect any validity check except checksum
|
||||||
|
require.NoError(t, builder.Load(roleNameToLoad, append(meta[roleNameToLoad], ' '), 0, false))
|
||||||
|
default:
|
||||||
|
require.NoError(t, builder.Load(roleNameToLoad, meta[roleNameToLoad], 0, false))
|
||||||
|
}
|
||||||
|
require.True(t, builder.IsLoaded(roleNameToLoad))
|
||||||
|
}
|
||||||
|
// now load the snapshot - it should fail with the checksum failure for the permuted role
|
||||||
|
err := builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 0, false)
|
||||||
|
switch roleNameToPermute {
|
||||||
|
case data.CanonicalTimestampRole:
|
||||||
|
require.NoError(t, err) // we don't check the timestamp's checksum
|
||||||
|
default:
|
||||||
|
require.Error(t, err)
|
||||||
|
checksumErr, ok := err.(data.ErrMismatchedChecksum)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Contains(t, checksumErr.Error(), fmt.Sprintf("checksum for %s did not match", roleNameToPermute))
|
||||||
|
require.False(t, builder.IsLoaded(data.CanonicalSnapshotRole))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// load all the metadata as is without alteration (so they should validate all checksums)
|
||||||
|
// but also load the metadata that is not contained in the snapshot. Then when the snapshot
|
||||||
|
// is loaded it will fail validation, because it doesn't have target/other/other's checksum
|
||||||
|
builder := tuf.NewRepoBuilder(nil, gun, nil, trustpinning.TrustPinConfig{})
|
||||||
|
for _, roleNameToLoad := range append(data.BaseRoles, "targets/other", "targets/other/other") {
|
||||||
|
if roleNameToLoad == data.CanonicalSnapshotRole {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
require.NoError(t, builder.Load(roleNameToLoad, meta[roleNameToLoad], 0, false))
|
||||||
|
}
|
||||||
|
err := builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 0, false)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.IsType(t, data.ErrMissingMeta{}, err)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue