Exclude translations from cookie banner version snapshots
Translation changes are cosmetic, not consent-contract changes, so they should not trigger a version bump. Translations are now loaded live from the database at serve time instead of being frozen in the snapshot. Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -472,7 +472,7 @@ func TestCookieBannerVersioning_ExcludedPattern(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCookieBannerVersioning_NoOpTranslation(t *testing.T) {
|
func TestCookieBannerVersioning_TranslationChangesNeverBump(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
t.Run("re-upserting identical JSON does not bump version", func(t *testing.T) {
|
t.Run("re-upserting identical JSON does not bump version", func(t *testing.T) {
|
||||||
@@ -481,13 +481,11 @@ func TestCookieBannerVersioning_NoOpTranslation(t *testing.T) {
|
|||||||
|
|
||||||
bannerID := factory.CreateCookieBanner(owner)
|
bannerID := factory.CreateCookieBanner(owner)
|
||||||
|
|
||||||
// Insert a custom translation, then publish.
|
|
||||||
const customJSON = `{"banner_title":"Cookie Bar","button_accept_all":"Accept"}`
|
const customJSON = `{"banner_title":"Cookie Bar","button_accept_all":"Accept"}`
|
||||||
upsertTranslation(t, owner, bannerID, "it", customJSON)
|
upsertTranslation(t, owner, bannerID, "it", customJSON)
|
||||||
published := publishBanner(t, owner, bannerID)
|
published := publishBanner(t, owner, bannerID)
|
||||||
baseline := published.Version
|
baseline := published.Version
|
||||||
|
|
||||||
// Re-upsert the same JSON.
|
|
||||||
upsertTranslation(t, owner, bannerID, "it", customJSON)
|
upsertTranslation(t, owner, bannerID, "it", customJSON)
|
||||||
|
|
||||||
got := latestVersion(t, owner, bannerID)
|
got := latestVersion(t, owner, bannerID)
|
||||||
@@ -495,25 +493,35 @@ func TestCookieBannerVersioning_NoOpTranslation(t *testing.T) {
|
|||||||
assert.Equal(t, "PUBLISHED", got.State)
|
assert.Equal(t, "PUBLISHED", got.State)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("whitespace-only and key-order differences do not bump version", func(t *testing.T) {
|
t.Run("changing translation content does not bump version", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||||
|
|
||||||
bannerID := factory.CreateCookieBanner(owner)
|
bannerID := factory.CreateCookieBanner(owner)
|
||||||
|
|
||||||
const compact = `{"banner_title":"Cookie Bar","button_accept_all":"Accept"}`
|
upsertTranslation(t, owner, bannerID, "it", `{"banner_title":"Cookie Bar"}`)
|
||||||
const reformatted = `{
|
|
||||||
"button_accept_all": "Accept",
|
|
||||||
"banner_title": "Cookie Bar"
|
|
||||||
}`
|
|
||||||
upsertTranslation(t, owner, bannerID, "it", compact)
|
|
||||||
published := publishBanner(t, owner, bannerID)
|
published := publishBanner(t, owner, bannerID)
|
||||||
baseline := published.Version
|
baseline := published.Version
|
||||||
|
|
||||||
upsertTranslation(t, owner, bannerID, "it", reformatted)
|
upsertTranslation(t, owner, bannerID, "it", `{"banner_title":"Barra dei Cookie"}`)
|
||||||
|
|
||||||
got := latestVersion(t, owner, bannerID)
|
got := latestVersion(t, owner, bannerID)
|
||||||
assert.Equal(t, baseline, got.Version, "JSON formatting differences should be canonicalised")
|
assert.Equal(t, baseline, got.Version, "translation content changes should not bump the version")
|
||||||
|
assert.Equal(t, "PUBLISHED", got.State)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("adding a new language does not bump version", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||||
|
|
||||||
|
bannerID := factory.CreateCookieBanner(owner)
|
||||||
|
published := publishBanner(t, owner, bannerID)
|
||||||
|
baseline := published.Version
|
||||||
|
|
||||||
|
upsertTranslation(t, owner, bannerID, "fr", `{"banner_title":"Bandeau cookies"}`)
|
||||||
|
|
||||||
|
got := latestVersion(t, owner, bannerID)
|
||||||
|
assert.Equal(t, baseline, got.Version, "adding a new language should not bump the version")
|
||||||
assert.Equal(t, "PUBLISHED", got.State)
|
assert.Equal(t, "PUBLISHED", got.State)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -358,9 +358,8 @@ func (s *Service) ensureDraftVersion(
|
|||||||
banner *coredata.CookieBanner,
|
banner *coredata.CookieBanner,
|
||||||
categories coredata.CookieCategories,
|
categories coredata.CookieCategories,
|
||||||
allPatterns coredata.CookiePatterns,
|
allPatterns coredata.CookiePatterns,
|
||||||
translations coredata.CookieBannerTranslations,
|
|
||||||
) (*coredata.CookieBannerVersion, error) {
|
) (*coredata.CookieBannerVersion, error) {
|
||||||
snapshot := buildSnapshot(banner, categories, allPatterns, translations)
|
snapshot := buildSnapshot(banner, categories, allPatterns)
|
||||||
|
|
||||||
var latest coredata.CookieBannerVersion
|
var latest coredata.CookieBannerVersion
|
||||||
err := latest.LoadLatestByCookieBannerID(ctx, tx, scope, banner.ID)
|
err := latest.LoadLatestByCookieBannerID(ctx, tx, scope, banner.ID)
|
||||||
@@ -440,12 +439,7 @@ func (s *Service) ensureDraftVersionForBanner(
|
|||||||
return nil, fmt.Errorf("cannot load cookie patterns: %w", err)
|
return nil, fmt.Errorf("cannot load cookie patterns: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var translations coredata.CookieBannerTranslations
|
return s.ensureDraftVersion(ctx, tx, scope, &banner, categories, allPatterns)
|
||||||
if err := translations.LoadAllByCookieBannerID(ctx, tx, scope, bannerID); err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot load cookie banner translations: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.ensureDraftVersion(ctx, tx, scope, &banner, categories, allPatterns, translations)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) CreateCookieBanner(
|
func (s *Service) CreateCookieBanner(
|
||||||
@@ -1888,7 +1882,18 @@ func (s *Service) GetActiveBannerConfig(
|
|||||||
return fmt.Errorf("cannot get version snapshot: %w", err)
|
return fmt.Errorf("cannot get version snapshot: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
config = buildBannerConfig(&banner, &version, &snapshot, lang)
|
var categories coredata.CookieCategories
|
||||||
|
if err := categories.LoadAllByCookieBannerID(ctx, conn, scope, banner.ID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load cookie categories: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var translations coredata.CookieBannerTranslations
|
||||||
|
if err := translations.LoadAllByCookieBannerID(ctx, conn, scope, banner.ID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load cookie banner translations: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resolved := resolveTranslations(translations, categories)
|
||||||
|
config = buildBannerConfig(&banner, &version, &snapshot, resolved, lang)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@@ -1904,6 +1909,7 @@ func buildBannerConfig(
|
|||||||
banner *coredata.CookieBanner,
|
banner *coredata.CookieBanner,
|
||||||
version *coredata.CookieBannerVersion,
|
version *coredata.CookieBannerVersion,
|
||||||
snapshot *coredata.CookieBannerVersionSnapshot,
|
snapshot *coredata.CookieBannerVersionSnapshot,
|
||||||
|
translations map[string]coredata.CookieBannerVersionSnapshotTranslation,
|
||||||
lang string,
|
lang string,
|
||||||
) *BannerConfig {
|
) *BannerConfig {
|
||||||
defaultLang := snapshot.DefaultLanguage
|
defaultLang := snapshot.DefaultLanguage
|
||||||
@@ -1913,7 +1919,7 @@ func buildBannerConfig(
|
|||||||
|
|
||||||
resolvedLang := defaultLang
|
resolvedLang := defaultLang
|
||||||
if lang != "" {
|
if lang != "" {
|
||||||
if _, ok := snapshot.Translations[lang]; ok {
|
if _, ok := translations[lang]; ok {
|
||||||
resolvedLang = lang
|
resolvedLang = lang
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1921,7 +1927,7 @@ func buildBannerConfig(
|
|||||||
categories := snapshot.Categories
|
categories := snapshot.Categories
|
||||||
texts := make(map[string]string)
|
texts := make(map[string]string)
|
||||||
|
|
||||||
if t, ok := snapshot.Translations[resolvedLang]; ok {
|
if t, ok := translations[resolvedLang]; ok {
|
||||||
maps.Copy(texts, t.UI)
|
maps.Copy(texts, t.UI)
|
||||||
|
|
||||||
if len(t.Categories) == len(categories) {
|
if len(t.Categories) == len(categories) {
|
||||||
@@ -2038,10 +2044,6 @@ func (s *Service) UpsertCookieBannerTranslation(
|
|||||||
return fmt.Errorf("cannot load cookie banner translation: %w", err)
|
return fmt.Errorf("cannot load cookie banner translation: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := s.ensureDraftVersionForBanner(ctx, tx, scope, req.CookieBannerID); err != nil {
|
|
||||||
return fmt.Errorf("cannot ensure draft version: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -49,14 +49,6 @@ func TestSnapshotsEqual(t *testing.T) {
|
|||||||
PostHogConsent: false,
|
PostHogConsent: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Translations: map[string]coredata.CookieBannerVersionSnapshotTranslation{
|
|
||||||
"fr": {
|
|
||||||
UI: map[string]string{"title": "Cookies"},
|
|
||||||
Categories: []coredata.CookieBannerVersionSnapshotCategoryTranslation{
|
|
||||||
{Name: "Analyse", Description: "Cookies d'analyse"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,19 +95,6 @@ func TestSnapshotsEqual(t *testing.T) {
|
|||||||
assert.False(t, snapshotsEqual(a, b))
|
assert.False(t, snapshotsEqual(a, b))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("differing translation UI is not equal", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
a := baseSnapshot()
|
|
||||||
b := baseSnapshot()
|
|
||||||
b.Translations["fr"] = coredata.CookieBannerVersionSnapshotTranslation{
|
|
||||||
UI: map[string]string{"title": "Cookies updated"},
|
|
||||||
Categories: a.Translations["fr"].Categories,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.False(t, snapshotsEqual(a, b))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("differing GCMConsentTypes order is not equal", func(t *testing.T) {
|
t.Run("differing GCMConsentTypes order is not equal", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -214,8 +193,8 @@ func TestBuildSnapshot_RankInvariant(t *testing.T) {
|
|||||||
t.Run("snapshot is identical regardless of rank values", func(t *testing.T) {
|
t.Run("snapshot is identical regardless of rank values", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
original := buildSnapshot(banner, mkCategories(0, 1, 2, 3), nil, nil)
|
original := buildSnapshot(banner, mkCategories(0, 1, 2, 3), nil)
|
||||||
shuffled := buildSnapshot(banner, mkCategories(99, 50, 25, 10), nil, nil)
|
shuffled := buildSnapshot(banner, mkCategories(99, 50, 25, 10), nil)
|
||||||
|
|
||||||
assert.True(t, snapshotsEqual(original, shuffled), "rank changes must not affect the snapshot")
|
assert.True(t, snapshotsEqual(original, shuffled), "rank changes must not affect the snapshot")
|
||||||
})
|
})
|
||||||
@@ -226,8 +205,8 @@ func TestBuildSnapshot_RankInvariant(t *testing.T) {
|
|||||||
ordered := mkCategories(0, 1, 2, 3)
|
ordered := mkCategories(0, 1, 2, 3)
|
||||||
reversed := coredata.CookieCategories{ordered[3], ordered[2], ordered[1], ordered[0]}
|
reversed := coredata.CookieCategories{ordered[3], ordered[2], ordered[1], ordered[0]}
|
||||||
|
|
||||||
a := buildSnapshot(banner, ordered, nil, nil)
|
a := buildSnapshot(banner, ordered, nil)
|
||||||
b := buildSnapshot(banner, reversed, nil, nil)
|
b := buildSnapshot(banner, reversed, nil)
|
||||||
|
|
||||||
assert.True(t, snapshotsEqual(a, b))
|
assert.True(t, snapshotsEqual(a, b))
|
||||||
})
|
})
|
||||||
@@ -235,7 +214,7 @@ func TestBuildSnapshot_RankInvariant(t *testing.T) {
|
|||||||
t.Run("Necessary comes first and Uncategorised comes last", func(t *testing.T) {
|
t.Run("Necessary comes first and Uncategorised comes last", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
snap := buildSnapshot(banner, mkCategories(0, 1, 2, 3), nil, nil)
|
snap := buildSnapshot(banner, mkCategories(0, 1, 2, 3), nil)
|
||||||
|
|
||||||
require.Len(t, snap.Categories, 4)
|
require.Len(t, snap.Categories, 4)
|
||||||
assert.Equal(t, coredata.CookieCategoryKindNecessary, snap.Categories[0].Kind)
|
assert.Equal(t, coredata.CookieCategoryKindNecessary, snap.Categories[0].Kind)
|
||||||
|
|||||||
@@ -24,6 +24,16 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// resolveTranslations converts raw DB translations into the resolved map
|
||||||
|
// used by buildBannerConfig at serve time. Categories must be sorted in
|
||||||
|
// snapshot order so the positional category translations align.
|
||||||
|
func resolveTranslations(
|
||||||
|
translations coredata.CookieBannerTranslations,
|
||||||
|
categories coredata.CookieCategories,
|
||||||
|
) map[string]coredata.CookieBannerVersionSnapshotTranslation {
|
||||||
|
return buildSnapshotTranslations(translations, sortCategoriesForSnapshot(categories))
|
||||||
|
}
|
||||||
|
|
||||||
// snapshotsEqual reports whether two version snapshots are visitor-identical.
|
// snapshotsEqual reports whether two version snapshots are visitor-identical.
|
||||||
// buildSnapshot already normalises empty slices and nil maps, so reflect.DeepEqual
|
// buildSnapshot already normalises empty slices and nil maps, so reflect.DeepEqual
|
||||||
// is sufficient and is the single chokepoint we'd extend if we ever wanted to
|
// is sufficient and is the single chokepoint we'd extend if we ever wanted to
|
||||||
@@ -67,7 +77,6 @@ func buildSnapshot(
|
|||||||
banner *coredata.CookieBanner,
|
banner *coredata.CookieBanner,
|
||||||
categories coredata.CookieCategories,
|
categories coredata.CookieCategories,
|
||||||
allPatterns coredata.CookiePatterns,
|
allPatterns coredata.CookiePatterns,
|
||||||
translations coredata.CookieBannerTranslations,
|
|
||||||
) coredata.CookieBannerVersionSnapshot {
|
) coredata.CookieBannerVersionSnapshot {
|
||||||
categories = sortCategoriesForSnapshot(categories)
|
categories = sortCategoriesForSnapshot(categories)
|
||||||
|
|
||||||
@@ -104,8 +113,6 @@ func buildSnapshot(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshotTranslations := buildSnapshotTranslations(translations, categories)
|
|
||||||
|
|
||||||
return coredata.CookieBannerVersionSnapshot{
|
return coredata.CookieBannerVersionSnapshot{
|
||||||
PrivacyPolicyURL: banner.PrivacyPolicyURL,
|
PrivacyPolicyURL: banner.PrivacyPolicyURL,
|
||||||
CookiePolicyURL: banner.CookiePolicyURL,
|
CookiePolicyURL: banner.CookiePolicyURL,
|
||||||
@@ -113,7 +120,6 @@ func buildSnapshot(
|
|||||||
ConsentMode: string(banner.ConsentMode),
|
ConsentMode: string(banner.ConsentMode),
|
||||||
DefaultLanguage: banner.DefaultLanguage,
|
DefaultLanguage: banner.DefaultLanguage,
|
||||||
Categories: snapshotCategories,
|
Categories: snapshotCategories,
|
||||||
Translations: snapshotTranslations,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ type (
|
|||||||
ConsentMode string `json:"consent_mode"`
|
ConsentMode string `json:"consent_mode"`
|
||||||
DefaultLanguage string `json:"default_language"`
|
DefaultLanguage string `json:"default_language"`
|
||||||
Categories []CookieBannerVersionSnapshotCategory `json:"categories"`
|
Categories []CookieBannerVersionSnapshotCategory `json:"categories"`
|
||||||
Translations map[string]CookieBannerVersionSnapshotTranslation `json:"translations,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CookieBannerVersionSnapshotTranslation struct {
|
CookieBannerVersionSnapshotTranslation struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user