- {gvlVendor ?
: null}
-
- {url?.privacy ? (
- Privacy policy
- ) : null}
- {url?.legIntClaim ? (
-
- Legitimate interest disclosure
-
- ) : null}
-
+
+ {hasUrls ? (
+
+ {vendor.privacy_policy_url ? (
+
+ Privacy policy
+
+ ) : null}
+ {vendor.legitimate_interest_disclosure_url ? (
+
+ Legitimate interest disclosure
+
+ ) : null}
+
+ ) : null}
;
purpose_consent_preferences?: Array;
purpose_legitimate_interests_preferences?: Array;
diff --git a/clients/fides-js/src/lib/tcf/types.ts b/clients/fides-js/src/lib/tcf/types.ts
index 6992d0051c..0d21c658d4 100644
--- a/clients/fides-js/src/lib/tcf/types.ts
+++ b/clients/fides-js/src/lib/tcf/types.ts
@@ -31,6 +31,12 @@ export type EmbeddedVendor = {
name: string;
};
+export type EmbeddedPurpose = {
+ id: number;
+ name: string;
+ retention_period?: string;
+};
+
// Purposes
export type TCFPurposeConsentRecord = {
id: number;
@@ -141,7 +147,7 @@ export type TCFVendorConsentRecord = {
outdated_preference?: UserConsentPreference;
current_served?: boolean;
outdated_served?: boolean;
- purpose_consents?: Array;
+ purpose_consents?: Array;
};
export type TCFVendorLegitimateInterestsRecord = {
@@ -154,7 +160,7 @@ export type TCFVendorLegitimateInterestsRecord = {
outdated_preference?: UserConsentPreference;
current_served?: boolean;
outdated_served?: boolean;
- purpose_legitimate_interests?: Array;
+ purpose_legitimate_interests?: Array;
};
export type TCFVendorRelationships = {
@@ -162,9 +168,15 @@ export type TCFVendorRelationships = {
has_vendor_id?: boolean;
name?: string;
description?: string;
- special_purposes?: Array;
+ special_purposes?: Array;
features?: Array;
special_features?: Array;
+ cookie_max_age_seconds?: number;
+ uses_cookies?: boolean;
+ cookie_refresh?: boolean;
+ uses_non_cookie_access?: boolean;
+ legitimate_interest_disclosure_url?: string;
+ privacy_policy_url?: string;
};
export type TCFVendorSave = {
@@ -272,16 +284,6 @@ export type GVLJson = Pick<
// GVL types—we should be able to get these from the library at some point,
// but since they are on GVL 2.2, the types aren't quite right for GVL 3.
-export interface GvlVendorUrl {
- langId: string;
- privacy?: string;
- legIntClaim?: string;
-}
-export interface GvlDataRetention {
- stdRetention: number;
- purposes: Record;
- specialPurposes: Record;
-}
interface GvlDataCategory {
id: number;
name: string;
diff --git a/clients/fides-js/src/lib/tcf/vendors.ts b/clients/fides-js/src/lib/tcf/vendors.ts
index b7aab96567..dec60dabd8 100644
--- a/clients/fides-js/src/lib/tcf/vendors.ts
+++ b/clients/fides-js/src/lib/tcf/vendors.ts
@@ -51,22 +51,12 @@ export const vendorIsAc = (vendorId: TCFVendorRelationships["id"]) =>
decodeVendorId(vendorId).source === VendorSources.AC;
export const uniqueGvlVendorIds = (experience: PrivacyExperience): number[] => {
- const {
- tcf_vendor_consents: vendorConsents = [],
- tcf_vendor_legitimate_interests: vendorLegints = [],
- } = experience;
+ const { tcf_vendor_relationships: vendors = [] } = experience;
- // List of i.e. [gvl.2, gacp.3, gvl.4]
- const universalIds = Array.from(
- new Set([
- ...vendorConsents.map((v) => v.id),
- ...vendorLegints.map((v) => v.id),
- ])
- );
// Filter to just i.e. [gvl.2, gvl.4]
- const gvlIds = universalIds.filter((uid) =>
- vendorGvlEntry(uid, experience.gvl)
- );
+ const gvlIds = vendors
+ .map((v) => v.id)
+ .filter((uid) => vendorGvlEntry(uid, experience.gvl));
// Return [2,4] as numbers
return gvlIds.map((uid) => +decodeVendorId(uid).id);
};
@@ -83,15 +73,10 @@ const transformVendorDataToVendorRecords = ({
isFidesSystem: boolean;
}) => {
const records: VendorRecord[] = [];
- const uniqueVendorIds = Array.from(
- new Set([...consents.map((c) => c.id), ...legints.map((l) => l.id)])
- );
- uniqueVendorIds.forEach((id) => {
- const vendorConsent = consents.find((v) => v.id === id);
- const vendorLegint = legints.find((v) => v.id === id);
- const relationship = relationships.find((r) => r.id === id);
+ relationships.forEach((relationship) => {
+ const vendorConsent = consents.find((v) => v.id === relationship.id);
+ const vendorLegint = legints.find((v) => v.id === relationship.id);
const record: VendorRecord = {
- id,
...relationship,
...vendorConsent,
...vendorLegint,
diff --git a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts
index 1f2b3c570f..8597b00d1e 100644
--- a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts
+++ b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts
@@ -276,6 +276,7 @@ describe("Fides-js TCF", () => {
cy.fixture("consent/experience_tcf.json").then((payload) => {
const experience = payload.items[0];
experience.tcf_vendor_consents.push(newVendor);
+ experience.tcf_vendor_relationships.push(newVendor);
stubConfig({
options: {
isOverlayEnabled: true,
@@ -316,14 +317,61 @@ describe("Fides-js TCF", () => {
});
cy.window().then((win) => {
win.__tcfapi("getTCData", 2, cy.stub().as("getTCData"));
+ cy.get("@getTCData")
+ .should("have.been.calledOnce")
+ .its("lastCall.args")
+ .then(([tcData, success]) => {
+ expect(success).to.eql(true);
+ expect(tcData.vendor.consents).to.eql({ 1: true, 2: true });
+ });
});
- cy.get("@getTCData")
- .should("have.been.calledOnce")
- .its("lastCall.args")
- .then(([tcData, success]) => {
- expect(success).to.eql(true);
- expect(tcData.vendor.consents).to.eql({ 1: true, 2: true });
+ });
+
+ it("can render extra vendor info such as cookie and retention data", () => {
+ cy.get("#fides-tab-Vendors").click();
+ cy.get(".fides-notice-toggle-title").contains(VENDOR_1.name).click();
+ cy.get(".fides-disclosure-visible").within(() => {
+ // Check urls
+ cy.get("a")
+ .contains("Privacy policy")
+ .should("have.attr", "href")
+ .and("contain", "https://www.example.com/privacy");
+ cy.get("a")
+ .contains("Legitimate interest disclosure")
+ .should("have.attr", "href")
+ .and(
+ "contain",
+ "https://www.example.com/legitimate_interest_disclosure"
+ );
+
+ // Check retention periods
+ [PURPOSE_4, PURPOSE_6, PURPOSE_7, PURPOSE_9].forEach((purpose) => {
+ // In the fixture, all retention periods are their id's
+ cy.get("tr")
+ .contains(purpose.name)
+ .parent()
+ .contains(`${purpose.id} day(s)`);
});
+ cy.get("tr")
+ .contains(SPECIAL_PURPOSE_1.name)
+ .parent()
+ .contains(`${SPECIAL_PURPOSE_1.id} day(s)`);
+
+ // Check cookie disclosure
+ cy.get("p").contains(
+ 'Captify stores cookies with a maximum duration of about 5 Day(s). These cookies may be refreshed. This vendor also uses other methods like "local storage" to store and access information on your device.'
+ );
+ });
+ // Check the cookie disclosure on the system
+ // First close the vendor
+ cy.get(".fides-notice-toggle-title").contains(VENDOR_1.name).click();
+ // Then open the system
+ cy.get(".fides-notice-toggle-title").contains(SYSTEM_1.name).click();
+ cy.get(".fides-disclosure-visible").within(() => {
+ cy.get("p").contains(
+ "Fides System stores cookies with a maximum duration of about 5 Day(s)"
+ );
+ });
});
it("can group toggle and fire FidesPreferenceToggled events", () => {
@@ -944,6 +992,7 @@ describe("Fides-js TCF", () => {
id: "test",
purpose_legitimate_interests: [{ id: 4, name: purpose4.name }],
});
+ experience.tcf_vendor_relationships?.push({ ...vendor, id: "test" });
stubConfig({
options: {
@@ -1533,13 +1582,13 @@ describe("Fides-js TCF", () => {
],
};
AC_IDS.forEach((id, idx) => {
+ const vendor = { ...baseVendor, id: `gacp.${id}`, name: `AC ${id}` };
experience.tcf_vendor_consents.push({
- ...baseVendor,
- id: `gacp.${id}`,
- name: `AC ${id}`,
+ ...vendor,
// Set some of these vendors without purpose_consents
purpose_consents: idx % 2 === 0 ? [] : baseVendor.purpose_consents,
});
+ experience.tcf_vendor_relationships.push(vendor);
});
stubConfig({
diff --git a/clients/privacy-center/cypress/fixtures/consent/experience_tcf.json b/clients/privacy-center/cypress/fixtures/consent/experience_tcf.json
index c65a801f5e..fd9c290ce0 100644
--- a/clients/privacy-center/cypress/fixtures/consent/experience_tcf.json
+++ b/clients/privacy-center/cypress/fixtures/consent/experience_tcf.json
@@ -281,19 +281,23 @@
"purpose_consents": [
{
"id": 4,
- "name": "Use profiles to select personalised advertising"
+ "name": "Use profiles to select personalised advertising",
+ "retention_period": "4"
},
{
"id": 6,
- "name": "Use profiles to select personalised content"
+ "name": "Use profiles to select personalised content",
+ "retention_period": "6"
},
{
"id": 7,
- "name": "Measure advertising performance"
+ "name": "Measure advertising performance",
+ "retention_period": "7"
},
{
"id": 9,
- "name": "Understand audiences through statistics or combinations of data from different sources"
+ "name": "Understand audiences through statistics or combinations of data from different sources",
+ "retention_period": "9"
}
]
}
@@ -301,6 +305,8 @@
"tcf_vendor_legitimate_interests": [],
"tcf_vendor_relationships": [
{
+ "cookie_max_age_seconds": 360000,
+ "cookie_refresh": true,
"id": "2",
"has_vendor_id": true,
"name": "Captify",
@@ -308,11 +314,16 @@
"special_purposes": [
{
"id": 1,
- "name": "Ensure security, prevent and detect fraud, and fix errors"
+ "name": "Ensure security, prevent and detect fraud, and fix errors",
+ "retention_period": "1"
}
],
+ "uses_cookies": true,
+ "uses_non_cookie_access": true,
"features": [],
- "special_features": []
+ "special_features": [],
+ "privacy_policy_url": "https://www.example.com/privacy",
+ "legitimate_interest_disclosure_url": "https://www.example.com/legitimate_interest_disclosure"
}
],
"tcf_consent_systems": [],
@@ -330,13 +341,16 @@
"purpose_legitimate_interests": [
{
"id": 2,
- "name": "Use limited data to select advertising"
+ "name": "Use limited data to select advertising",
+ "retention_period": "2"
}
]
}
],
"tcf_system_relationships": [
{
+ "cookie_max_age_seconds": 400000,
+ "cookie_refresh": false,
"id": "ctl_b3dde2d5-e535-4d9a-bf6e-a3b6beb01761",
"has_vendor_id": false,
"name": "Fides System",
@@ -344,7 +358,8 @@
"special_purposes": [
{
"id": 1,
- "name": "Ensure security, prevent and detect fraud, and fix errors"
+ "name": "Ensure security, prevent and detect fraud, and fix errors",
+ "retention_period": "1"
}
],
"features": [
@@ -353,7 +368,9 @@
"name": "Match and combine data from other data sources"
}
],
- "special_features": []
+ "special_features": [],
+ "uses_cookies": true,
+ "uses_non_cookie_access": false
}
],
"created_at": "2023-09-26T18:59:40.416181+00:00",
diff --git a/clients/privacy-center/types/api/models/SavePrivacyPreferencesResponse.ts b/clients/privacy-center/types/api/models/SavePrivacyPreferencesResponse.ts
index 086759e8ee..dea544de10 100644
--- a/clients/privacy-center/types/api/models/SavePrivacyPreferencesResponse.ts
+++ b/clients/privacy-center/types/api/models/SavePrivacyPreferencesResponse.ts
@@ -3,6 +3,7 @@
/* eslint-disable */
import type { CurrentPrivacyPreferenceSchema } from "./CurrentPrivacyPreferenceSchema";
+import type { TCMobileData } from "./TCMobileData";
/**
* Response schema when saving privacy preferences
@@ -18,4 +19,5 @@ export type SavePrivacyPreferencesResponse = {
special_feature_preferences?: Array;
system_consent_preferences?: Array;
system_legitimate_interests_preferences?: Array;
+ fides_mobile_data?: TCMobileData;
};
diff --git a/src/fides/api/schemas/tcf.py b/src/fides/api/schemas/tcf.py
index e16ee3932b..509edb1722 100644
--- a/src/fides/api/schemas/tcf.py
+++ b/src/fides/api/schemas/tcf.py
@@ -67,6 +67,12 @@ class EmbeddedLineItem(FidesSchema):
name: str
+class EmbeddedPurpose(EmbeddedLineItem):
+ """Sparse details for an embedded purpose beneath a system or vendor section. Read-only."""
+
+ retention_period: Optional[str]
+
+
class CommonVendorFields(FidesSchema):
"""Fields shared between the three vendor sections of the TCF Experience"""
@@ -79,7 +85,7 @@ class CommonVendorFields(FidesSchema):
class TCFVendorConsentRecord(UserSpecificConsentDetails, CommonVendorFields):
"""Schema for a TCF Vendor with Consent legal basis"""
- purpose_consents: List[EmbeddedLineItem] = []
+ purpose_consents: List[EmbeddedPurpose] = []
@root_validator
def add_default_preference(cls, values: Dict[str, Any]) -> Dict[str, Any]:
@@ -94,7 +100,7 @@ class TCFVendorLegitimateInterestsRecord(
):
"""Schema for a TCF Vendor with Legitimate interests legal basis"""
- purpose_legitimate_interests: List[EmbeddedLineItem] = []
+ purpose_legitimate_interests: List[EmbeddedPurpose] = []
@root_validator
def add_default_preference(cls, values: Dict[str, Any]) -> Dict[str, Any]:
@@ -107,7 +113,7 @@ def add_default_preference(cls, values: Dict[str, Any]) -> Dict[str, Any]:
class TCFVendorRelationships(CommonVendorFields):
"""Collects the other relationships for a given vendor - no preferences are saved here"""
- special_purposes: List[EmbeddedLineItem] = []
+ special_purposes: List[EmbeddedPurpose] = []
features: List[EmbeddedLineItem] = []
special_features: List[EmbeddedLineItem] = []
cookie_max_age_seconds: Optional[int]
@@ -115,6 +121,7 @@ class TCFVendorRelationships(CommonVendorFields):
cookie_refresh: Optional[bool]
uses_non_cookie_access: Optional[bool]
legitimate_interest_disclosure_url: Optional[AnyUrl]
+ privacy_policy_url: Optional[AnyUrl]
class TCFFeatureRecord(NonVendorSection, Feature):
diff --git a/src/fides/api/util/tcf/tcf_experience_contents.py b/src/fides/api/util/tcf/tcf_experience_contents.py
index 0135e09427..90e78aca89 100644
--- a/src/fides/api/util/tcf/tcf_experience_contents.py
+++ b/src/fides/api/util/tcf/tcf_experience_contents.py
@@ -28,6 +28,7 @@
)
from fides.api.schemas.base_class import FidesSchema
from fides.api.schemas.tcf import (
+ EmbeddedPurpose,
EmbeddedVendor,
TCFFeatureRecord,
TCFPurposeConsentRecord,
@@ -205,10 +206,12 @@ def get_matching_privacy_declarations(db: Session) -> Query:
System.legitimate_interest_disclosure_url.label(
"system_legitimate_interest_disclosure_url"
),
+ System.privacy_policy.label("system_privacy_policy"),
System.vendor_id,
PrivacyDeclaration.data_use,
PrivacyDeclaration.legal_basis_for_processing,
PrivacyDeclaration.features,
+ PrivacyDeclaration.retention_period,
)
.outerjoin(PrivacyDeclaration, System.id == PrivacyDeclaration.system_id)
.filter(
@@ -305,6 +308,8 @@ def _add_top_level_record_to_purpose_or_feature_section(
def _embed_purpose_or_feature_under_system(
embedded_tcf_record: NonVendorRecord,
system_section: SystemSubSections,
+ retention_period: Optional[str],
+ is_purpose_section: bool,
) -> None:
"""
Embed a second-level TCF purpose/feature under the systems section.
@@ -323,8 +328,18 @@ def _embed_purpose_or_feature_under_system(
if embedded_non_vendor_record:
return
- # Nest new cloned TCF purpose or feature record beneath system otherwise
- system_section.append(embedded_tcf_record) # type: ignore[arg-type]
+ if is_purpose_section:
+ # Build the EmbeddedPurpose record with the retention period
+ system_section.append(
+ EmbeddedPurpose( # type: ignore[arg-type]
+ id=embedded_tcf_record.id,
+ name=embedded_tcf_record.name,
+ retention_period=retention_period,
+ )
+ )
+ else:
+ # Nest new cloned feature record beneath system otherwise
+ system_section.append(embedded_tcf_record)
def _embed_system_under_purpose_or_feature(
@@ -432,6 +447,8 @@ def build_purpose_or_feature_section_and_update_vendor_map(
system_section=getattr(
vendor_map[system_identifier], vendor_subsection_name
),
+ retention_period=privacy_declaration_row.retention_period,
+ is_purpose_section=is_purpose_section,
)
# Finally, nest the system beneath this top level non-vendor tcf record
@@ -520,6 +537,9 @@ def populate_vendor_relationships_basic_attributes(
vendor_relationship_record.legitimate_interest_disclosure_url = (
privacy_declaration_row.system_legitimate_interest_disclosure_url
)
+ vendor_relationship_record.privacy_policy_url = (
+ privacy_declaration_row.system_privacy_policy
+ )
return vendor_map
diff --git a/tests/fixtures/application_fixtures.py b/tests/fixtures/application_fixtures.py
index b659255d5c..235ca66f02 100644
--- a/tests/fixtures/application_fixtures.py
+++ b/tests/fixtures/application_fixtures.py
@@ -2807,6 +2807,7 @@ def tcf_system(db: Session) -> System:
"legal_basis_for_processing": "Consent",
"egress": None,
"ingress": None,
+ "retention_period": "3",
},
)
@@ -2823,6 +2824,7 @@ def tcf_system(db: Session) -> System:
"legal_basis_for_processing": "Legitimate interests",
"egress": None,
"ingress": None,
+ "retention_period": "1",
},
)
diff --git a/tests/ops/util/test_tcf_experience_contents.py b/tests/ops/util/test_tcf_experience_contents.py
index a13b6ba12e..7daf0891d1 100644
--- a/tests/ops/util/test_tcf_experience_contents.py
+++ b/tests/ops/util/test_tcf_experience_contents.py
@@ -1,8 +1,10 @@
+from uuid import uuid4
+
import pytest
from fideslang import MAPPED_PURPOSES
from fideslang.models import LegalBasisForProcessingEnum
-from fides.api.models.sql_models import PrivacyDeclaration
+from fides.api.models.sql_models import PrivacyDeclaration, System
from fides.api.schemas.tcf import EmbeddedVendor
from fides.api.util.tcf.tcf_experience_contents import get_tcf_contents
@@ -231,6 +233,7 @@ def test_system_has_declaration_no_features_special_features_special_purposes(
assert vendor_relationship.uses_non_cookie_access is False
assert vendor_relationship.cookie_refresh is False
assert vendor_relationship.legitimate_interest_disclosure_url is None
+ assert vendor_relationship.privacy_policy_url is None
@pytest.mark.usefixtures("tcf_system")
def test_system_exists_with_tcf_purpose_and_vendor(self, db):
@@ -274,6 +277,7 @@ def test_system_exists_with_tcf_purpose_and_vendor(self, db):
assert not hasattr(
tcf_contents.tcf_vendor_consents[0], "legitimate_interest_disclosure_url"
)
+ assert not hasattr(tcf_contents.tcf_vendor_consents[0], "privacy_policy_url")
assert len(tcf_contents.tcf_vendor_consents[0].purpose_consents) == 1
assert tcf_contents.tcf_vendor_consents[0].purpose_consents[0].id == 8
@@ -294,8 +298,15 @@ def test_system_exists_with_tcf_purpose_and_vendor(self, db):
tcf_contents.tcf_vendor_relationships[0].legitimate_interest_disclosure_url
is None
)
+ assert tcf_contents.tcf_vendor_relationships[0].privacy_policy_url is None
assert len(tcf_contents.tcf_vendor_relationships[0].special_purposes) == 1
assert tcf_contents.tcf_vendor_relationships[0].special_purposes[0].id == 1
+ assert (
+ tcf_contents.tcf_vendor_relationships[0]
+ .special_purposes[0]
+ .retention_period
+ == "1"
+ )
def test_system_exists_with_tcf_purpose_and_vendor_including_tcf_fields_set(
self, db, tcf_system
@@ -309,6 +320,7 @@ def test_system_exists_with_tcf_purpose_and_vendor_including_tcf_fields_set(
tcf_system.cookie_refresh = True
tcf_system.uses_non_cookie_access = True
tcf_system.legitimate_interest_disclosure_url = "http://test.com/disclosure_url"
+ tcf_system.privacy_policy = "http://test.com/privacy_url"
tcf_system.save(db)
tcf_contents = get_tcf_contents(db)
@@ -350,9 +362,14 @@ def test_system_exists_with_tcf_purpose_and_vendor_including_tcf_fields_set(
assert not hasattr(
tcf_contents.tcf_vendor_consents[0], "legitimate_interest_disclosure_url"
)
+ assert not hasattr(tcf_contents.tcf_vendor_consents[0], "privacy_policy_url")
assert len(tcf_contents.tcf_vendor_consents[0].purpose_consents) == 1
assert tcf_contents.tcf_vendor_consents[0].purpose_consents[0].id == 8
+ assert (
+ tcf_contents.tcf_vendor_consents[0].purpose_consents[0].retention_period
+ == "3"
+ )
assert tcf_contents.tcf_vendor_relationships[0].id == "gvl.42"
assert tcf_contents.tcf_vendor_relationships[0].name == "TCF System Test"
@@ -372,6 +389,10 @@ def test_system_exists_with_tcf_purpose_and_vendor_including_tcf_fields_set(
tcf_contents.tcf_vendor_relationships[0].legitimate_interest_disclosure_url
== "http://test.com/disclosure_url"
)
+ assert (
+ tcf_contents.tcf_vendor_relationships[0].privacy_policy_url
+ == "http://test.com/privacy_url"
+ )
assert len(tcf_contents.tcf_vendor_relationships[0].special_purposes) == 1
assert tcf_contents.tcf_vendor_relationships[0].special_purposes[0].id == 1
@@ -542,6 +563,75 @@ def test_system_matches_subset_of_purpose_data_uses(self, db, tcf_system):
assert len(tcf_contents.tcf_vendor_consents[0].purpose_consents) == 1
assert tcf_contents.tcf_vendor_consents[0].purpose_consents[0].id == 2
+ @pytest.mark.usefixtures("tcf_system")
+ def test_two_vendors_same_purpose_different_retention_period(self, db, tcf_system):
+ """Test making sure that two vendors that share the same purpose show up with
+ different retention periods in their EmbeddedPurposes"""
+
+ # Create a second system with the same privacy declaration as tcf_system
+ # but with a different retention period
+ second_system = System.create(
+ db=db,
+ data={
+ "fides_key": f"tcf-system_key-f{uuid4()}",
+ "vendor_id": "gvl.100",
+ "name": f"TCF System Second Test",
+ "description": "My Second TCF System Description",
+ "organization_fides_key": "default_organization",
+ "system_type": "Service",
+ "data_responsibility_title": "Processor",
+ "data_protection_impact_assessment": {
+ "is_required": False,
+ "progress": None,
+ "link": None,
+ },
+ },
+ )
+
+ PrivacyDeclaration.create(
+ db=db,
+ data={
+ "name": "Collect data for content performance",
+ "system_id": second_system.id,
+ "data_categories": ["user.device.cookie_id"],
+ "data_use": "analytics.reporting.content_performance",
+ "data_qualifier": "aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified",
+ "data_subjects": ["customer"],
+ "dataset_references": None,
+ "legal_basis_for_processing": "Consent",
+ "egress": None,
+ "ingress": None,
+ "retention_period": "5", # the fixture already has a retention_period of 3
+ },
+ )
+
+ tcf_contents = get_tcf_contents(db)
+
+ assert_length_of_tcf_sections(
+ tcf_contents,
+ p_c_len=1,
+ p_li_len=0,
+ f_len=0,
+ sp_len=1,
+ sf_len=0,
+ v_c_len=2,
+ v_li_len=0,
+ v_r_len=2,
+ s_c_len=0,
+ s_li_len=0,
+ s_r_len=0,
+ )
+
+ assert tcf_contents.tcf_vendor_consents[0].id == "gvl.100"
+ assert tcf_contents.tcf_vendor_consents[0].purpose_consents == [
+ {"id": 8, "name": "Measure content performance", "retention_period": "5"}
+ ]
+
+ assert tcf_contents.tcf_vendor_consents[1].id == "gvl.42"
+ assert tcf_contents.tcf_vendor_consents[1].purpose_consents == [
+ {"id": 8, "name": "Measure content performance", "retention_period": "3"}
+ ]
+
@pytest.mark.usefixtures("tcf_system")
def test_special_purposes(self, db):
tcf_contents = get_tcf_contents(db)
@@ -773,6 +863,12 @@ def test_duplicate_data_uses_on_system(self, tcf_system, db):
.id
== 3
)
+ assert (
+ tcf_contents.tcf_vendor_legitimate_interests[0]
+ .purpose_legitimate_interests[0]
+ .retention_period
+ == "1"
+ )
def test_add_different_data_uses_that_correspond_to_same_purpose(
self, tcf_system, db