Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions internal/sync/api/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,25 @@ func TestClientPatchesSyncManifest(t *testing.T) {
}`, string(request.Body))
}

// LaunchDarkly answers a project and a source that no sync wrote yet with an
// empty manifest that has no formatVersion. That is an empty manifest, not
// an unsupported one.
func TestClientAcceptsAnUnwrittenSyncManifest(t *testing.T) {
transport := &recordingClient{Responses: [][]byte{[]byte(`{"items":[],"source":"git:example.com/acme/repo"}`)}}
client := NewClient(transport, "token", "https://example.com")

manifest, err := client.GetSyncManifest("project", "git:example.com/acme/repo")

require.NoError(t, err)
assert.Empty(t, manifest.Items)
assert.True(t, manifest.Unwritten())

transport = &recordingClient{Responses: [][]byte{[]byte(`{"items":[{"resourceKind":"variation","resourceLookupKey":"support/helpful","fingerprint":"sha256:abc","version":1}],"source":"git:example.com/acme/repo"}`)}}
client = NewClient(transport, "token", "https://example.com")
_, err = client.GetSyncManifest("project", "git:example.com/acme/repo")
require.ErrorContains(t, err, "unsupported formatVersion 0", "entries without a format version are not an unwritten manifest")
}

func TestClientRejectsUnknownSyncManifestVersion(t *testing.T) {
transport := &recordingClient{Responses: [][]byte{[]byte(`{
"formatVersion": 99,
Expand Down
15 changes: 12 additions & 3 deletions internal/sync/api/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,20 @@ func (client Client) PatchSyncManifest(
return manifest, nil
}

// Unwritten reports whether the manifest is the one that LaunchDarkly returns
// for a project and a source that no sync wrote yet: no entries and no format
// version. Sync treats it as an empty manifest of the supported format.
func (manifest SyncManifest) Unwritten() bool {
return manifest.FormatVersion == 0 && len(manifest.Items) == 0
}

// validateSyncManifest accepts a manifest of the supported format, and an
// unwritten manifest, which has no format to check.
func validateSyncManifest(manifest SyncManifest) error {
if manifest.FormatVersion != SyncManifestFormatVersion {
return fmt.Errorf("unsupported formatVersion %d", manifest.FormatVersion)
if manifest.Unwritten() || manifest.FormatVersion == SyncManifestFormatVersion {
return nil
}
return nil
return fmt.Errorf("unsupported formatVersion %d", manifest.FormatVersion)
}

func manifestPath(projectKey string) []string {
Expand Down
4 changes: 3 additions & 1 deletion internal/sync/manifest/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ func (store Store) patch(
}

func (store Store) resourcesFromRemote(projectKey string, remote syncapi.SyncManifest) ([]Resource, error) {
if remote.FormatVersion != syncapi.SyncManifestFormatVersion {
// A project and a source that no sync wrote yet have an empty manifest
// without a format version.
if !remote.Unwritten() && remote.FormatVersion != syncapi.SyncManifestFormatVersion {
return nil, fmt.Errorf(
"sync manifest for project %q has unsupported formatVersion %d",
projectKey,
Expand Down
33 changes: 33 additions & 0 deletions internal/sync/manifest/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,39 @@ func (client *manifestClient) PatchSyncManifest(
return response, nil
}

// A project that no sync wrote yet has an empty manifest without a format
// version in LaunchDarkly. It loads as no entries, and a manifest with
// entries still needs the supported format.
func TestStoreLoadsAnUnwrittenManifestAsEmpty(t *testing.T) {
client := &manifestClient{manifests: map[string]syncapi.SyncManifest{
"fresh": {Source: "git:example/repo"},
"written": {
FormatVersion: syncapi.SyncManifestFormatVersion,
Source: "git:example/repo",
Items: []syncapi.SyncManifestResource{{
ResourceKind: syncdomain.KindVariation, ResourceLookupKey: "config/a",
Fingerprint: fingerprint("a"), Version: 2,
}},
},
}}

loaded, err := NewStore(client, "git:example/repo").Load([]string{"fresh", "written"})

require.NoError(t, err)
require.Len(t, loaded.Resources, 1)
assert.Equal(t, "written", loaded.Resources[0].ProjectKey)

client.manifests["fresh"] = syncapi.SyncManifest{
Source: "git:example/repo",
Items: []syncapi.SyncManifestResource{{
ResourceKind: syncdomain.KindVariation, ResourceLookupKey: "config/b",
Fingerprint: fingerprint("b"), Version: 1,
}},
}
_, err = NewStore(client, "git:example/repo").Load([]string{"fresh"})
require.ErrorContains(t, err, "unsupported formatVersion 0")
}

func TestStoreLoadsProjectManifests(t *testing.T) {
client := &manifestClient{manifests: map[string]syncapi.SyncManifest{
"zeta": {
Expand Down
Loading