| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package config_validator |
| |
| import ( |
| "config" |
| "errors" |
| "math" |
| "testing" |
| ) |
| |
| // makeValidReport returns a valid instance of config.ReportDefinition which |
| // can be modified to fail various validation checks for testing purposes. |
| func makeValidReport() *config.ReportDefinition { |
| return makeValidReportWithNameAndType("the_report_name", config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS) |
| } |
| |
| func makeValidReportWithType(t config.ReportDefinition_ReportType) *config.ReportDefinition { |
| return makeValidReportWithNameAndType("the_report_name", t) |
| } |
| |
| func makeValidReportWithName(name string) *config.ReportDefinition { |
| return makeValidReportWithNameAndType(name, config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS) |
| } |
| |
| func makeValidReportWithNameAndType(name string, t config.ReportDefinition_ReportType) *config.ReportDefinition { |
| return &config.ReportDefinition{ |
| Id: 10, |
| ReportName: name, |
| ReportType: t, |
| PrivacyLevel: config.ReportDefinition_NO_ADDED_PRIVACY, |
| PrivacyMechanism: config.ReportDefinition_DE_IDENTIFICATION, |
| } |
| } |
| |
| // Given a metric type |mt|, returns a valid report type for that metric type. |
| // Prefers to return a non-histogram report type if the given metric type supports |
| // one, to aid in checking where int_buckets get set in tests. |
| func getMatchingReportType(mt config.MetricDefinition_MetricType) config.ReportDefinition_ReportType { |
| switch mt { |
| case config.MetricDefinition_OCCURRENCE: |
| return config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS |
| case config.MetricDefinition_INTEGER: |
| return config.ReportDefinition_FLEETWIDE_MEANS |
| case config.MetricDefinition_INTEGER_HISTOGRAM: |
| return config.ReportDefinition_FLEETWIDE_HISTOGRAMS |
| case config.MetricDefinition_STRING: |
| return config.ReportDefinition_STRING_COUNTS |
| } |
| |
| return config.ReportDefinition_REPORT_TYPE_UNSET |
| } |
| |
| // Test that makeValidReport returns a valid report. |
| func TestValidateMakeValidReport(t *testing.T) { |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| r := makeValidReport() |
| if err := validateReportDefinition(m, r); err != nil { |
| t.Errorf("Rejected valid report: %v", err) |
| } |
| } |
| |
| func TestDifferentReportId(t *testing.T) { |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| r := makeValidReport() |
| r.Id += 1 |
| |
| if err := validateReportDefinition(m, r); err != nil { |
| t.Error("Reject report with different report id.") |
| } |
| } |
| |
| func TestValidateReportHasNoAppVersionField(t *testing.T) { |
| r := makeValidReport() |
| |
| r.SystemProfileField = []config.SystemProfileField{ |
| config.SystemProfileField_CHANNEL, config.SystemProfileField_APP_VERSION} |
| if err := validateReportHasNoAppVersionField(r); err == nil { |
| t.Error("Did not detect app_version field.") |
| } |
| |
| r.SystemProfileField = []config.SystemProfileField{ |
| config.SystemProfileField_CHANNEL} |
| if err := validateReportHasNoAppVersionField(r); err != nil { |
| t.Error("Detected app_version field - although there was none.") |
| } |
| } |
| |
| func TestNoAppVersionFieldOnFuchsiaErrorCase(t *testing.T) { |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| r := makeValidReport() |
| |
| m.CustomerId = math.MaxInt32 |
| r.SystemProfileField = []config.SystemProfileField{ |
| config.SystemProfileField_APP_VERSION} |
| if err := validateReportDefinitionForMetric(m, r); err == nil { |
| t.Error("Accepted report with set app_version field on Fuchsia.") |
| } |
| } |
| |
| func TestNoAppVersionFieldOnFuchsiaNoFuchsiaCase(t *testing.T) { |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| r := makeValidReport() |
| |
| m.CustomerId = 3 |
| r.SystemProfileField = []config.SystemProfileField{ |
| config.SystemProfileField_APP_VERSION} |
| if err := validateReportDefinitionForMetric(m, r); err != nil { |
| t.Error("Didn't accept app_version field, although it wasn't Fuchsia.") |
| } |
| } |
| |
| func TestNoAppVersionFieldOnFuchsiaNoAppVersionField(t *testing.T) { |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| r := makeValidReport() |
| |
| m.CustomerId = 1 |
| r.SystemProfileField = []config.SystemProfileField{ |
| config.SystemProfileField_CHANNEL} |
| if err := validateReportDefinitionForMetric(m, r); err != nil { |
| t.Error("Did not accept ordinary report.") |
| } |
| } |
| |
| func TestValidateInvalidName(t *testing.T) { |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| r := makeValidReportWithName("_invalid_name") |
| |
| if err := validateReportDefinition(m, r); err == nil { |
| t.Error("Accepted report with invalid name.") |
| } |
| } |
| |
| func TestValidateZeroReportId(t *testing.T) { |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| r := makeValidReportWithName("NRaMinLNcqiYmgEypLLVGnXymNpxJzqabtbbjLycCMEohvVzZtAYpah") |
| r.Id = 0 |
| |
| if err := validateReportDefinition(m, r); err == nil { |
| t.Error("Accepted report with 0 id.") |
| } |
| } |
| |
| func TestValidateUnsetReportType(t *testing.T) { |
| if err := validateReportType(config.MetricDefinition_OCCURRENCE, config.ReportDefinition_REPORT_TYPE_UNSET); err == nil { |
| t.Error("Accepted report with no report type set.") |
| } |
| } |
| |
| func TestValidatePoissonFields(t *testing.T) { |
| r := makeValidReport() |
| r.ReportType = config.ReportDefinition_UNIQUE_DEVICE_COUNTS |
| |
| r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY |
| r.PoissonMean = 1.0 |
| if err := validatePoissonFields(r); err == nil { |
| t.Error("Accepted report definition with `poisson_mean` set but with NO_ADDED_PRIVACY privacy level") |
| } |
| |
| r.PrivacyLevel = config.ReportDefinition_HIGH_PRIVACY |
| r.PoissonMean = 0.0 |
| if err := validatePoissonFields(r); err == nil { |
| t.Error("Accepted report definition with a nontrivial privacy level, but without `poisson_mean`") |
| } |
| |
| r.PoissonMean = -1.0 |
| if err := validatePoissonFields(r); err == nil { |
| t.Error("Accepted report definition with a nontrivial privacy level, but negative `poisson_mean`") |
| } |
| |
| r.PoissonMean = 1.0 |
| if err := validatePoissonFields(r); err == nil { |
| t.Error("Accepted report definition with a nontrivial privacy level, but `num_index_points` unset") |
| } |
| |
| r.NumIndexPoints = 1 |
| if err := validatePoissonFields(r); err != nil { |
| t.Error("Rejected report definition with a nontrivial privacy level and valid privacy settings") |
| } |
| |
| stringSketchParams := config.StringSketchParameters{ |
| NumCellsPerHash: 10, |
| NumHashes: 5, |
| } |
| |
| r.ReportType = config.ReportDefinition_STRING_COUNTS |
| if err := validatePoissonFields(r); err == nil { |
| t.Error("Accepted STRING_COUNTS report definition with a nontrivial privacy level, but `string_sketch_params` unset") |
| } |
| r.ReportType = config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS |
| if err := validatePoissonFields(r); err == nil { |
| t.Error("Accepted UNIQUE_DEVICE_STRING_COUNTS report definition with a nontrivial privacy level, but `string_sketch_params` unset") |
| } |
| |
| r.StringSketchParams = &stringSketchParams |
| r.ReportType = config.ReportDefinition_STRING_COUNTS |
| if err := validatePoissonFields(r); err != nil { |
| t.Error("Rejected STRING_COUNTS report definition with a nontrivial privacy level and valid privacy settings") |
| } |
| r.ReportType = config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS |
| if err := validatePoissonFields(r); err != nil { |
| t.Error("Rejected UNIQUE_DEVICE_STRING_COUTNS report definition with a nontrivial privacy level and valid privacy settings") |
| } |
| } |
| |
| func TestValidateMinValueMaxValueNoAddedPrivacy(t *testing.T) { |
| var reportTypes = []config.ReportDefinition_ReportType{ |
| config.ReportDefinition_UNIQUE_DEVICE_COUNTS, |
| config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS, |
| config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS, |
| config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS, |
| config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS, |
| config.ReportDefinition_FLEETWIDE_HISTOGRAMS, |
| config.ReportDefinition_FLEETWIDE_MEANS, |
| config.ReportDefinition_STRING_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS, |
| } |
| type args struct { |
| minValue int64 |
| maxValue int64 |
| } |
| var tests = []struct { |
| input args |
| valid bool |
| }{ |
| // Valid reports: no added privacy, no bounds set |
| {args{0, 0}, true}, |
| // Invalid reports: no added privacy, at least one of min_value and max_value set |
| {args{0, 1}, false}, |
| {args{1, 0}, false}, |
| {args{1, 1}, false}, |
| } |
| for _, reportType := range reportTypes { |
| for _, test := range tests { |
| r := makeValidReportWithType(reportType) |
| r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY |
| r.MinValue = test.input.minValue |
| r.MaxValue = test.input.maxValue |
| err := validateMinValueMaxValue(r) |
| |
| if test.valid && err != nil { |
| t.Errorf("rejected valid report with type %v and (min_value, max_value) = %v error: %v", |
| reportType, test.input, err) |
| } else if !test.valid && err == nil { |
| t.Errorf("accepted invalid report with type %v and (min_value, max_value) = %v", |
| reportType, test.input) |
| } |
| } |
| } |
| } |
| |
| func TestValidateMinValueMaxValueWithAddedPrivacy(t *testing.T) { |
| var required = []config.ReportDefinition_ReportType{ |
| config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS, |
| config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS, |
| config.ReportDefinition_FLEETWIDE_MEANS, |
| } |
| var notAllowed = []config.ReportDefinition_ReportType{ |
| config.ReportDefinition_UNIQUE_DEVICE_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS, |
| config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS, |
| config.ReportDefinition_FLEETWIDE_HISTOGRAMS, |
| config.ReportDefinition_STRING_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS, |
| } |
| type args struct { |
| reportTypes []config.ReportDefinition_ReportType |
| minValue int64 |
| maxValue int64 |
| } |
| var tests = []struct { |
| input args |
| valid bool |
| }{ |
| // Valid reports: min value and max value should not be set |
| {args{notAllowed, 0, 0}, true}, |
| // Valid reports: at least one of min value and max value should be set |
| {args{required, 0, 1}, true}, |
| {args{required, -1, 1}, true}, |
| {args{required, 1, 1}, true}, |
| // Invalid reports: min value and max value should not be set |
| {args{notAllowed, 0, 1}, false}, |
| {args{notAllowed, -1, 0}, false}, |
| // Invalid reports: at least one of min value and max value should be set |
| {args{required, 0, 0}, false}, |
| // Invalid reports: min value is larger than max value |
| {args{required, 2, 1}, false}, |
| } |
| for _, test := range tests { |
| for _, reportType := range test.input.reportTypes { |
| r := makeValidReportWithType(reportType) |
| r.PrivacyLevel = config.ReportDefinition_LOW_PRIVACY |
| r.MinValue = test.input.minValue |
| r.MaxValue = test.input.maxValue |
| err := validateMinValueMaxValue(r) |
| |
| if test.valid && err != nil { |
| t.Errorf("rejected valid report of type %v with error: %v", reportType, err) |
| } else if !test.valid && err == nil { |
| t.Errorf("accepted invalid report of type %v and (min_value, max_value) = (%d, %d)", |
| reportType, test.input.minValue, test.input.maxValue) |
| } |
| } |
| } |
| } |
| |
| func TestValidateMaxCountNoAddedPrivacy(t *testing.T) { |
| var reportTypes = []config.ReportDefinition_ReportType{ |
| config.ReportDefinition_UNIQUE_DEVICE_COUNTS, |
| config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS, |
| config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS, |
| config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS, |
| config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS, |
| config.ReportDefinition_FLEETWIDE_HISTOGRAMS, |
| config.ReportDefinition_FLEETWIDE_MEANS, |
| config.ReportDefinition_STRING_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS, |
| } |
| var tests = []struct { |
| maxCount uint64 |
| valid bool |
| }{ |
| // Valid reports: no added privacy, no bounds set |
| {0, true}, |
| // Invalid reports: no added privacy, max_count set |
| {1, false}, |
| } |
| for _, reportType := range reportTypes { |
| for _, test := range tests { |
| r := makeValidReportWithType(reportType) |
| r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY |
| r.MaxCount = test.maxCount |
| err := validateMaxCount(r) |
| |
| if test.valid && err != nil { |
| t.Errorf("rejected valid report with type %v and max_count = %d error: %v", |
| reportType, test.maxCount, err) |
| } else if !test.valid && err == nil { |
| t.Errorf("accepted invalid report with type %v and max_count = %d", |
| reportType, test.maxCount) |
| } |
| } |
| } |
| } |
| |
| func TestValidateMaxCountWithAddedPrivacy(t *testing.T) { |
| var required = []config.ReportDefinition_ReportType{ |
| config.ReportDefinition_FLEETWIDE_HISTOGRAMS, |
| config.ReportDefinition_FLEETWIDE_MEANS, |
| config.ReportDefinition_STRING_COUNTS, |
| } |
| var notAllowed = []config.ReportDefinition_ReportType{ |
| config.ReportDefinition_UNIQUE_DEVICE_COUNTS, |
| config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS, |
| config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS, |
| config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS, |
| config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS, |
| config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS, |
| } |
| type args struct { |
| reportTypes []config.ReportDefinition_ReportType |
| maxCount uint64 |
| } |
| var tests = []struct { |
| input args |
| valid bool |
| }{ |
| // Valid reports: max count should be set |
| {args{required, 1}, true}, |
| // Valid reports: max count should not be set |
| {args{notAllowed, 0}, true}, |
| // Valid reports: max count should be set |
| {args{required, 1}, true}, |
| // Invalid reports: max count should not be set |
| {args{notAllowed, 1}, false}, |
| // Invalid reports: max count should be set |
| {args{required, 0}, false}, |
| } |
| for _, test := range tests { |
| for _, reportType := range test.input.reportTypes { |
| r := makeValidReportWithType(reportType) |
| r.PrivacyLevel = config.ReportDefinition_LOW_PRIVACY |
| r.MaxCount = test.input.maxCount |
| err := validateMaxCount(r) |
| |
| if test.valid && err != nil { |
| t.Errorf("rejected valid report of type %v with error: %v", reportType, err) |
| } else if !test.valid && err == nil { |
| t.Errorf("accepted invalid report of type %v and max_count = %d", |
| reportType, test.input.maxCount) |
| } |
| } |
| } |
| } |
| |
| // Test that int_buckets is not set if the report type is STRING_COUNTS. |
| func TestValidateReportDefinitionForStringCounts(t *testing.T) { |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| r := makeValidReport() |
| r.ReportType = config.ReportDefinition_STRING_COUNTS |
| r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY |
| r.StringBufferMax = 10 |
| |
| if err := validateReportDefinition(m, r); err != nil { |
| t.Errorf("Failed validation for valid report of type STRING_COUNTS: %v", err) |
| } |
| |
| r.IntBuckets = &config.IntegerBuckets{} |
| if err := validateReportDefinition(m, r); err == nil { |
| t.Error("Accepted report definition of type STRING_COUNTS with int_buckets specified.") |
| } |
| } |
| |
| // Test that int_buckets is not set if the report type is UNIQUE_DEVICE_STRING_COUNTS. |
| func TestValidateReportDefitionForUniqueDeviceStringCounts(t *testing.T) { |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| r := makeValidReport() |
| r.ReportType = config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS |
| r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY |
| r.LocalAggregationPeriod = config.WindowSize_WINDOW_1_DAY |
| r.StringBufferMax = 10 |
| |
| if err := validateReportDefinition(m, r); err != nil { |
| t.Errorf("Failed validation for valid report of type UNIQUE_DEVICE_STRING_COUNTS: %v", err) |
| } |
| |
| r.IntBuckets = &config.IntegerBuckets{} |
| if err := validateReportDefinition(m, r); err == nil { |
| t.Errorf("Accepted report definition of type UNIQUE_DEVICE_STRING_COUNTS with int_buckets specified.") |
| } |
| } |
| |
| func TestValidateLocalAggregationPeriod(t *testing.T) { |
| dailyReportTypes := []config.ReportDefinition_ReportType{ |
| config.ReportDefinition_UNIQUE_DEVICE_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS, |
| config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS, |
| config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS, |
| } |
| hourlyReportTypes := []config.ReportDefinition_ReportType{ |
| config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS, |
| config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS, |
| config.ReportDefinition_FLEETWIDE_HISTOGRAMS, |
| config.ReportDefinition_FLEETWIDE_MEANS, |
| config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS, |
| config.ReportDefinition_STRING_COUNTS, |
| } |
| |
| for _, reportType := range dailyReportTypes { |
| r := makeValidReportWithType(reportType) |
| |
| if err := validateLocalAggregationPeriod(r); err == nil { |
| t.Error("Accepted daily report with no local_aggregation_period") |
| } |
| |
| r.LocalAggregationPeriod = config.WindowSize_WINDOW_1_DAY |
| if err := validateLocalAggregationPeriod(r); err != nil { |
| t.Errorf("Rejected daily report with valid local_aggregation_period: %v", err) |
| } |
| } |
| |
| for _, reportType := range hourlyReportTypes { |
| r := makeValidReportWithType(reportType) |
| |
| r.LocalAggregationPeriod = config.WindowSize_WINDOW_1_DAY |
| if err := validateLocalAggregationPeriod(r); err == nil { |
| t.Error("Accepted hourly report with local_aggregation_period") |
| } |
| |
| r.LocalAggregationPeriod = config.WindowSize_UNSET |
| if err := validateLocalAggregationPeriod(r); err != nil { |
| t.Errorf("Rejected hourly report with no local_aggregation_period: %v", err) |
| } |
| } |
| } |
| |
| func TestValidateLocalAggregationProcedure(t *testing.T) { |
| intLocalAggregationProcedureTypes := []config.ReportDefinition_ReportType{ |
| config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS, |
| config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS, |
| config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS, |
| config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS, |
| } |
| |
| for _, reportType := range intLocalAggregationProcedureTypes { |
| m := makeValidMetric(config.MetricDefinition_INTEGER) |
| r := makeValidReportWithType(reportType) |
| |
| if err := validateLocalAggregationProcedure(m, r); err == nil { |
| t.Error("Accepted invalid report with no local_aggregation_procedure") |
| } |
| |
| r.LocalAggregationProcedure = config.ReportDefinition_AT_LEAST_ONCE |
| if err := validateLocalAggregationProcedure(m, r); err == nil { |
| t.Error("Accepted report with invalid local_aggregation_procedure") |
| } |
| |
| r.LocalAggregationProcedure = config.ReportDefinition_MEDIAN |
| if err := validateLocalAggregationProcedure(m, r); err != nil { |
| t.Errorf("Rejected report with valid local_aggregation_procedure: %v", err) |
| } |
| |
| r.LocalAggregationProcedure = config.ReportDefinition_PERCENTILE_N |
| if err := validateLocalAggregationProcedure(m, r); err == nil { |
| t.Error("Accepted PERCENTILE_N report with no local_aggregation_procedure_percentile_n set") |
| } |
| r.LocalAggregationProcedurePercentileN = 110 |
| if err := validateLocalAggregationProcedure(m, r); err == nil { |
| t.Error("Accepted PERCENTILE_N report with invalid local_aggregation_procedure_percentile_n set") |
| } |
| r.LocalAggregationProcedurePercentileN = 5 |
| if err := validateLocalAggregationProcedure(m, r); err != nil { |
| t.Errorf("Rejected PERCENTILE_N report with valid local_aggregation_procedure_percentile_n: %v", err) |
| } |
| |
| m = makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| if err := validateLocalAggregationProcedure(m, r); err == nil { |
| t.Error("Accepted daily report with no local_aggregation_procedure") |
| } |
| |
| r.LocalAggregationProcedure = config.ReportDefinition_LOCAL_AGGREGATION_PROCEDURE_UNSET |
| if err := validateLocalAggregationProcedure(m, r); err != nil { |
| t.Errorf("Rejected daily report with valid local_aggregation_procedure: %v", err) |
| } |
| } |
| |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| r := makeValidReportWithType(config.ReportDefinition_UNIQUE_DEVICE_COUNTS) |
| if err := validateLocalAggregationProcedure(m, r); err == nil { |
| t.Error("Accepted invalid report with no local_aggregation_procedure") |
| } |
| r.LocalAggregationProcedure = config.ReportDefinition_MEDIAN |
| if err := validateLocalAggregationProcedure(m, r); err == nil { |
| t.Error("Accepted report with invalid local_aggregation_procedure") |
| } |
| r.LocalAggregationProcedure = config.ReportDefinition_AT_LEAST_ONCE |
| if err := validateLocalAggregationProcedure(m, r); err != nil { |
| t.Errorf("Rejected report with valid local_aggregation_procedure: %v", err) |
| } |
| |
| r.LocalAggregationProcedure = config.ReportDefinition_SELECT_FIRST |
| r.EventVectorBufferMax = 1 |
| if err := validateLocalAggregationProcedure(m, r); err != nil { |
| t.Errorf("Rejected report with valid event_vector_buffer_max config: %v", err) |
| } |
| |
| r.EventVectorBufferMax = 100 |
| if err := validateLocalAggregationProcedure(m, r); err == nil { |
| t.Errorf("Accepted report with invalid event_vector_buffer_max config") |
| } |
| |
| r = makeValidReportWithType(config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS) |
| r.LocalAggregationProcedure = config.ReportDefinition_AT_LEAST_ONCE |
| if err := validateLocalAggregationProcedure(m, r); err == nil { |
| t.Error("Accepted report with unnecessary local_aggregation_procedure") |
| } |
| } |
| |
| func TestValidateExpeditedSending(t *testing.T) { |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| r := makeValidReportWithType(config.ReportDefinition_UNIQUE_DEVICE_COUNTS) |
| r.ExpeditedSending = true |
| r.LocalAggregationProcedure = config.ReportDefinition_SELECT_MOST_COMMON |
| r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY |
| if err := validateExpeditedSending(m, r); err == nil { |
| t.Error("Accepted report with invalid local_aggregation_procedure for expedited_sending") |
| } |
| r.LocalAggregationProcedure = config.ReportDefinition_AT_LEAST_ONCE |
| if err := validateExpeditedSending(m, r); err != nil { |
| t.Errorf("Rejected report with expedited_sending and valid local_aggregation_procedure: %v", err) |
| } |
| r.LocalAggregationProcedure = config.ReportDefinition_SELECT_FIRST |
| if err := validateExpeditedSending(m, r); err != nil { |
| t.Errorf("Rejected report with expedited_sending and valid local_aggregation_procedure: %v", err) |
| } |
| r.PrivacyLevel = config.ReportDefinition_LOW_PRIVACY |
| if err := validateExpeditedSending(m, r); err == nil { |
| t.Error("Accepted report with invalid privacy_level for expedited_sending") |
| } |
| |
| r = makeValidReportWithType(config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS) |
| r.ExpeditedSending = true |
| r.LocalAggregationProcedure = config.ReportDefinition_AT_LEAST_ONCE |
| if err := validateExpeditedSending(m, r); err == nil { |
| t.Error("Accepted report with invalid type for expedited_sending") |
| } |
| r.ExpeditedSending = false |
| r.LocalAggregationProcedure = config.ReportDefinition_SELECT_MOST_COMMON |
| if err := validateExpeditedSending(m, r); err != nil { |
| t.Errorf("Rejected report with expedited_sending unset: %v", err) |
| } |
| |
| m = makeValidMetric(config.MetricDefinition_STRING) |
| r = makeValidReportWithType(config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS) |
| r.ExpeditedSending = true |
| r.LocalAggregationProcedure = config.ReportDefinition_LOCAL_AGGREGATION_PROCEDURE_UNSET |
| r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY |
| if err := validateExpeditedSending(m, r); err != nil { |
| t.Errorf("Rejected report with expedited_sending and valid report type: %v", err) |
| } |
| } |
| |
| func TestValidateMaxReleaseStage(t *testing.T) { |
| // Default valid metric uses DEBUG max_release_stage. |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| r := makeValidReportWithType(config.ReportDefinition_UNIQUE_DEVICE_COUNTS) |
| if err := validateMaxReleaseStage(m, r); err != nil { |
| t.Errorf("Rejected report with no max_release_stage: %v", err) |
| } |
| r.MaxReleaseStage = config.ReleaseStage_DEBUG |
| if err := validateMaxReleaseStage(m, r); err != nil { |
| t.Errorf("Rejected report with the same max_release_stage as the metric: %v", err) |
| } |
| r.MaxReleaseStage = config.ReleaseStage_FISHFOOD |
| if err := validateMaxReleaseStage(m, r); err == nil { |
| t.Error("Accepted report with max_release_stage later than in the metric") |
| } |
| m.MetaData.MaxReleaseStage = config.ReleaseStage_FISHFOOD |
| if err := validateMaxReleaseStage(m, r); err != nil { |
| t.Errorf("Rejected report with the same max_release_stage as the metric: %v", err) |
| } |
| m.MetaData.MaxReleaseStage = config.ReleaseStage_DOGFOOD |
| if err := validateMaxReleaseStage(m, r); err != nil { |
| t.Errorf("Rejected report with max_release_stage earlier than the metric: %v", err) |
| } |
| } |
| |
| func TestValidateExemptFromConsent(t *testing.T) { |
| r := makeValidReportWithType(config.ReportDefinition_UNIQUE_DEVICE_COUNTS) |
| r.ExemptFromConsent = true |
| r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY |
| if err := validateExemptFromConsent(r); err == nil { |
| t.Error("Accepted report with no differential privacy and exempt_from_consent set") |
| } |
| r.PrivacyLevel = config.ReportDefinition_LOW_PRIVACY |
| if err := validateExemptFromConsent(r); err != nil { |
| t.Errorf("Rejected report with valid exempt_from_consent: %v", err) |
| } |
| } |
| |
| func TestValidateMaxPrivateIndex(t *testing.T) { |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| r := makeValidReportWithType(config.ReportDefinition_UNIQUE_DEVICE_COUNTS) |
| r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY |
| if err := validateMaxPrivateIndex(m, r); err != nil { |
| t.Errorf("Rejected report with valid max private index: %v", err) |
| } |
| r.PrivacyLevel = config.ReportDefinition_HIGH_PRIVACY |
| if err := validateMaxPrivateIndex(m, r); err != nil { |
| t.Errorf("Rejected private UNIQUE_DEVICE_COUNTS report with valid max private index: %v", err) |
| } |
| m.MetricDimensions = append(m.MetricDimensions, &config.MetricDefinition_MetricDimension{ |
| Dimension: "Dimension with lots of event codes", |
| MaxEventCode: math.MaxInt32 + 1, // > INT32_MAX |
| }) |
| if err := validateMaxPrivateIndex(m, r); err == nil { |
| t.Error("Accepted private UNIQUE_DEVICE_COUNTS report with too many private indices") |
| } |
| |
| m = makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| m.MetricDimensions = append(m.MetricDimensions, &config.MetricDefinition_MetricDimension{ |
| Dimension: "Dimension with lots of event codes", |
| MaxEventCode: 1000, |
| }) |
| r = makeValidReportWithType(config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS) |
| r.PrivacyLevel = config.ReportDefinition_HIGH_PRIVACY |
| r.NumIndexPoints = 10 |
| if err := validateMaxPrivateIndex(m, r); err != nil { |
| t.Errorf("Rejected private FLEETWIDE_OCCURRENCE_COUNTS report with valid max private index: %v", err) |
| } |
| r.NumIndexPoints = math.MaxInt32 / 10 // * 1,000 * 10 will be > INT32_MAX |
| if err := validateMaxPrivateIndex(m, r); err == nil { |
| t.Error("Accepted private FLEETWIDE_OCCURRENCE_COUNTS report with too many private indices") |
| } |
| |
| m = makeValidMetric(config.MetricDefinition_INTEGER) |
| m.MetricDimensions = append(m.MetricDimensions, &config.MetricDefinition_MetricDimension{ |
| Dimension: "Dimension with lots of event codes", |
| MaxEventCode: 1000, |
| }) |
| r = makeValidReportWithType(config.ReportDefinition_FLEETWIDE_MEANS) |
| r.PrivacyLevel = config.ReportDefinition_HIGH_PRIVACY |
| r.NumIndexPoints = 10 |
| if err := validateMaxPrivateIndex(m, r); err != nil { |
| t.Errorf("Rejected private FLEETWIDE_MEANS report with valid max private index: %v", err) |
| } |
| r.NumIndexPoints = math.MaxInt32 / 100 // * 1,000 * 10 will be > INT32_MAX |
| if err := validateMaxPrivateIndex(m, r); err == nil { |
| t.Error("Accepted private FLEETWIDE_MEANS report with too many private indices") |
| } |
| |
| r = makeValidReportWithType(config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS) |
| r.PrivacyLevel = config.ReportDefinition_HIGH_PRIVACY |
| r.IntBuckets = &config.IntegerBuckets{ |
| Buckets: &config.IntegerBuckets_Linear{ |
| Linear: &config.LinearIntegerBuckets{ |
| NumBuckets: 1000, |
| }, |
| }, |
| } |
| if err := validateMaxPrivateIndex(m, r); err != nil { |
| t.Errorf("Rejected private UNIQUE_DEVICE_HISTOGRAMS report with valid max private index: %v", err) |
| } |
| r.IntBuckets.GetLinear().NumBuckets = 10000000 // 10,000,000 * 1,000 > INT32_MAX |
| if err := validateMaxPrivateIndex(m, r); err == nil { |
| t.Error("Accepted private UNIQUE_DEVICE_HISTOGRAMS report with too many private indices") |
| } |
| |
| m = makeValidMetric(config.MetricDefinition_INTEGER_HISTOGRAM) |
| m.MetricDimensions = append(m.MetricDimensions, &config.MetricDefinition_MetricDimension{ |
| Dimension: "Dimension with lots of event codes", |
| MaxEventCode: 1000, |
| }) |
| m.IntBuckets = &config.IntegerBuckets{ |
| Buckets: &config.IntegerBuckets_Linear{ |
| Linear: &config.LinearIntegerBuckets{ |
| NumBuckets: 1000, |
| }, |
| }, |
| } |
| r = makeValidReportWithType(config.ReportDefinition_FLEETWIDE_HISTOGRAMS) |
| r.PrivacyLevel = config.ReportDefinition_HIGH_PRIVACY |
| r.NumIndexPoints = 1000 |
| if err := validateMaxPrivateIndex(m, r); err != nil { |
| t.Errorf("Rejected private FLEETWIDE_HISTOGRAMS report with valid max private index: %v", err) |
| } |
| m.IntBuckets.GetLinear().NumBuckets = 10000 // 10,000 * 1,000 * 1,000 > INT32_MAX |
| if err := validateMaxPrivateIndex(m, r); err == nil { |
| t.Error("Accepted private FLEETWIDE_HISTOGRAMS report with too many private indices") |
| } |
| |
| m = makeValidMetric(config.MetricDefinition_STRING) |
| m.MetricDimensions = append(m.MetricDimensions, &config.MetricDefinition_MetricDimension{ |
| Dimension: "Dimension with lots of event codes", |
| MaxEventCode: 1000, |
| }) |
| r = makeValidReportWithType(config.ReportDefinition_STRING_COUNTS) |
| r.PrivacyLevel = config.ReportDefinition_HIGH_PRIVACY |
| if err := validateMaxPrivateIndex(m, r); err != nil { |
| t.Errorf("Rejected private STRING_COUNTS report with valid max private index: %v", err) |
| } |
| m.MetricDimensions = append(m.MetricDimensions, &config.MetricDefinition_MetricDimension{ |
| Dimension: "Dimension with lots of event codes", |
| MaxEventCode: 1000000, // 1,000,000 * 1,000 * 50 (string count-min constants) will be > INT32_MAX |
| }) |
| if err := validateMaxPrivateIndex(m, r); err == nil { |
| t.Error("Accepted private STRING_COUNTS report with too many private indices") |
| } |
| } |
| |
| func TestValidateNoHourlyReports(t *testing.T) { |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| dailyReportTypes := []config.ReportDefinition_ReportType{ |
| config.ReportDefinition_UNIQUE_DEVICE_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS, |
| config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS, |
| config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS, |
| } |
| hourlyReportTypes := []config.ReportDefinition_ReportType{ |
| config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS, |
| config.ReportDefinition_FLEETWIDE_HISTOGRAMS, |
| config.ReportDefinition_FLEETWIDE_MEANS, |
| config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS, |
| config.ReportDefinition_STRING_COUNTS, |
| } |
| |
| for _, reportType := range dailyReportTypes { |
| r := makeValidReportWithType(reportType) |
| if err := validateNoHourlyReports(m, r); err != nil { |
| t.Errorf("Rejected daily report: %v", err) |
| } |
| } |
| |
| for _, reportType := range hourlyReportTypes { |
| r := makeValidReportWithType(reportType) |
| if err := validateNoHourlyReports(m, r); err == nil { |
| t.Error("Accepted hourly report") |
| } |
| } |
| |
| // Test FLEETWIDE_OCCURRENCE_COUNTS, which may define a non-hourly reporting |
| // interval. |
| r := makeValidReportWithType(config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS) |
| // Test unset reporting interval. |
| err := validateNoHourlyReports(m, r) |
| if err == nil { |
| t.Error("Accepted hourly FLEETWIDE_OCCURRENCE_COUNTS report with default reporting interval") |
| } |
| if !errors.Is(err, errIllegalHourlyReport) { |
| t.Errorf("Incorrect error return type: %v", err) |
| } |
| |
| // Test HOURS_1 reporting interval. |
| r.ReportingInterval = config.ReportDefinition_HOURS_1 |
| err = validateNoHourlyReports(m, r) |
| if err == nil { |
| t.Error("Accepted hourly FLEETWIDE_OCCURRENCE_COUNTS report with hourly reporting interval") |
| } |
| if !errors.Is(err, errIllegalHourlyReport) { |
| t.Errorf("Incorrect error return type: %v", err) |
| } |
| |
| // Test DAYS_1 reporting interval. |
| r.ReportingInterval = config.ReportDefinition_DAYS_1 |
| if err := validateNoHourlyReports(m, r); err != nil { |
| t.Errorf("Rejected daily FLEETWIDE_OCCURRENCE_COUNTS report with daily reporting interval: %v", err) |
| } |
| |
| // Test STRING_COUNTS, which may define a non-hourly reporting |
| // interval. |
| r = makeValidReportWithType(config.ReportDefinition_STRING_COUNTS) |
| // Test unset reporting interval. |
| err = validateNoHourlyReports(m, r) |
| if err == nil { |
| t.Error("Accepted hourly STRING_COUNTS report with default reporting interval") |
| } |
| if !errors.Is(err, errIllegalHourlyReport) { |
| t.Errorf("Incorrect error return type: %v", err) |
| } |
| |
| // Test HOURS_1 reporting interval. |
| r.ReportingInterval = config.ReportDefinition_HOURS_1 |
| err = validateNoHourlyReports(m, r) |
| if err == nil { |
| t.Error("Accepted hourly STRING_COUNTS report with hourly reporting interval") |
| } |
| if !errors.Is(err, errIllegalHourlyReport) { |
| t.Errorf("Incorrect error return type: %v", err) |
| } |
| |
| // Test DAYS_1 reporting interval. |
| r.ReportingInterval = config.ReportDefinition_DAYS_1 |
| if err := validateNoHourlyReports(m, r); err != nil { |
| t.Errorf("Rejected daily STRING_COUNTS report with daily reporting interval: %v", err) |
| } |
| } |
| |
| func TestValidateEnabledReportingInterval(t *testing.T) { |
| unsupportedReportTypes := []config.ReportDefinition_ReportType{ |
| config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS, |
| config.ReportDefinition_FLEETWIDE_HISTOGRAMS, |
| config.ReportDefinition_FLEETWIDE_MEANS, |
| config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS, |
| config.ReportDefinition_UNIQUE_DEVICE_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS, |
| config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS, |
| config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS, |
| } |
| |
| supportedReportTypes := []config.ReportDefinition_ReportType{ |
| config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS, |
| config.ReportDefinition_STRING_COUNTS, |
| } |
| |
| reportingIntervalValues := []config.ReportDefinition_ReportingInterval{ |
| config.ReportDefinition_HOURS_1, |
| config.ReportDefinition_DAYS_1, |
| } |
| |
| for _, reportType := range supportedReportTypes { |
| // Check supported report types. |
| r := makeValidReportWithType(reportType) |
| |
| // Unset reporting interval is valid. |
| if err := validateReportingInterval(r, true); err != nil { |
| t.Errorf("Rejected report with no reporting interval set: %v", err) |
| } |
| |
| // All reporting interval values are valid. |
| for _, reportingInterval := range reportingIntervalValues { |
| r.ReportingInterval = reportingInterval |
| if err := validateReportingInterval(r, true); err != nil { |
| t.Errorf("Rejected report with valid %v reporting interval set: %v", reportingInterval, err) |
| } |
| } |
| } |
| |
| // Check unsupported report types. |
| for _, reportType := range unsupportedReportTypes { |
| r := makeValidReportWithType(reportType) |
| |
| // Unset reporting interval is valid. |
| if err := validateReportingInterval(r, true); err != nil { |
| t.Errorf("Rejected report with no reporting interval set: %v", err) |
| } |
| |
| // Only the unset reporting interval is allowed on non-supported report types. |
| for _, reportingInterval := range reportingIntervalValues { |
| r.ReportingInterval = reportingInterval |
| if err := validateReportingInterval(r, true); err == nil { |
| t.Errorf("No error returned for report type %v with reporting interval %v", r.ReportType, r.ReportingInterval) |
| } |
| } |
| } |
| } |
| |
| func TestValidateDisabledReportingInterval(t *testing.T) { |
| allReportTypes := []config.ReportDefinition_ReportType{ |
| config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS, |
| config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS, |
| config.ReportDefinition_FLEETWIDE_HISTOGRAMS, |
| config.ReportDefinition_FLEETWIDE_MEANS, |
| config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS, |
| config.ReportDefinition_STRING_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS, |
| config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS, |
| config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS, |
| } |
| |
| reportingIntervalValues := []config.ReportDefinition_ReportingInterval{ |
| config.ReportDefinition_HOURS_1, |
| config.ReportDefinition_DAYS_1, |
| } |
| |
| for _, reportType := range allReportTypes { |
| r := makeValidReportWithType(reportType) |
| |
| // Unset reporting interval is valid. |
| if err := validateReportingInterval(r, false); err != nil { |
| t.Errorf("Rejected report with no reporting interval set: %v", err) |
| } |
| |
| // Verify an error is returned when setting reporting interval when the feature is disabled. |
| for _, reportingInterval := range reportingIntervalValues { |
| r.ReportingInterval = reportingInterval |
| if err := validateReportingInterval(r, false); err == nil { |
| t.Errorf("No error returned for report type %v with reporting interval %v", r.ReportType, r.ReportingInterval) |
| } |
| } |
| } |
| } |
| |
| // Test that deleted report IDs are not reused. |
| func TestDeletedReportIds(t *testing.T) { |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| r := makeValidReportWithType(config.ReportDefinition_UNIQUE_DEVICE_COUNTS) |
| r.LocalAggregationPeriod = config.WindowSize_WINDOW_1_DAY |
| r.LocalAggregationProcedure = config.ReportDefinition_AT_LEAST_ONCE |
| r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY |
| m.DeletedReportIds = append(m.DeletedReportIds, r.Id+10) |
| |
| // Test that using a new report ID returns no error. |
| if err := validateReportDefinitionForMetric(m, r); err != nil { |
| t.Errorf("Rejected report with new report ID: %v", err) |
| } |
| |
| // Test that reusing a previously deleted report ID returns an error. |
| m.DeletedReportIds = append(m.DeletedReportIds, r.Id) |
| if err := validateReportDefinitionForMetric(m, r); err == nil { |
| t.Errorf("Accepted invalid report with reused deleted report ID") |
| } |
| } |
| |
| func TestValidatePrivacyMechanismAndConfig(t *testing.T) { |
| // Default valid metric uses DEBUG max_release_stage. |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| r := makeValidReportWithType(config.ReportDefinition_UNIQUE_DEVICE_COUNTS) |
| if err := validatePrivacyMechanismAndConfig(m, r); err != nil { |
| t.Errorf("Rejected report with no privacy mechanism: %v", err) |
| } |
| |
| r.PrivacyMechanism = config.ReportDefinition_DE_IDENTIFICATION |
| if err := validatePrivacyMechanismAndConfig(m, r); err != nil { |
| t.Errorf("Rejected report with privacy mechanism DE_IDENTIFICATION: %v", err) |
| } |
| |
| r.PrivacyMechanism = config.ReportDefinition_SHUFFLED_DIFFERENTIAL_PRIVACY |
| if err := validatePrivacyMechanismAndConfig(m, r); err == nil { |
| t.Errorf("Didn't rejected report with privacy mechanism SHUFFLED_DIFFERENTIAL_PRIVACY and empty config: %v", err) |
| } |
| |
| r.PrivacyConfig = &config.ReportDefinition_ShuffledDp{ |
| ShuffledDp: &config.ReportDefinition_ShuffledDifferentialPrivacyConfig{ |
| Epsilon: 1, |
| Delta: 0.001, |
| ReportingThreshold: 10000, |
| PoissonMean: 1, |
| }, |
| } |
| if err := validatePrivacyMechanismAndConfig(m, r); err != nil { |
| t.Errorf("Rejected report with privacy mechanism SHUFFLED_DIFFERENTIAL_PRIVACY and not empty config: %v", err) |
| } |
| } |
| |
| func TestValidateShuffledDpReportPrivacy(t *testing.T) { |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| r := makeValidReportWithType(config.ReportDefinition_UNIQUE_DEVICE_COUNTS) |
| |
| r.LocalAggregationProcedure = config.ReportDefinition_AT_LEAST_ONCE |
| r.PrivacyMechanism = config.ReportDefinition_SHUFFLED_DIFFERENTIAL_PRIVACY |
| r.PrivacyConfig = &config.ReportDefinition_ShuffledDp{ |
| ShuffledDp: &config.ReportDefinition_ShuffledDifferentialPrivacyConfig{ |
| Epsilon: 1, |
| Delta: 1e-10, |
| ReportingThreshold: 1000, |
| PoissonMean: 0.085937, |
| }, |
| } |
| |
| if err := validateReportPoissonMean(m, r); err != nil { |
| t.Errorf("Rejected report with valid privacy parameters with: %v", err) |
| } |
| |
| r.PrivacyConfig.(*config.ReportDefinition_ShuffledDp).ShuffledDp.PoissonMean = 0.07 |
| if err := validateReportPoissonMean(m, r); err == nil { |
| t.Error("Accepted report with invalid privacy parameters") |
| } |
| } |
| |
| func TestValidateShuffledDpConfigWithValidConfig(t *testing.T) { |
| c := &config.ReportDefinition_ShuffledDifferentialPrivacyConfig{ |
| Epsilon: 1, |
| Delta: 0.001, |
| ReportingThreshold: 10000, |
| PoissonMean: 1.0, |
| } |
| |
| if err := validateShuffledDpConfig(c); err != nil { |
| t.Errorf("Rejected valid config: %v", err) |
| } |
| } |
| |
| func TestValidateShuffledDpConfigWithInvalidEpsilon(t *testing.T) { |
| invalidEpsilons := [2]float64{-1, 0} |
| for _, e := range invalidEpsilons { |
| c := &config.ReportDefinition_ShuffledDifferentialPrivacyConfig{ |
| Epsilon: e, |
| Delta: 0.001, |
| ReportingThreshold: 10000, |
| PoissonMean: 1, |
| } |
| if err := validateShuffledDpConfig(c); err == nil { |
| t.Errorf("Didn't reject config with invalid Epsilon: %f", e) |
| } |
| } |
| } |
| |
| func TestValidateShuffledDpConfigWithInvalidDelta(t *testing.T) { |
| invalidDeltas := [4]float64{0, 1, 125, -1} |
| for _, d := range invalidDeltas { |
| c := &config.ReportDefinition_ShuffledDifferentialPrivacyConfig{ |
| Epsilon: 1, |
| Delta: d, |
| ReportingThreshold: 10000, |
| } |
| if err := validateShuffledDpConfig(c); err == nil { |
| t.Errorf("Didn't reject config with invalid Delta: %f", d) |
| } |
| } |
| } |
| |
| func TestValidateShuffledDpConfigWithInvalidReportingThreshold(t *testing.T) { |
| c := &config.ReportDefinition_ShuffledDifferentialPrivacyConfig{ |
| Epsilon: 1, |
| Delta: 0.001, |
| ReportingThreshold: 0, |
| } |
| if err := validateShuffledDpConfig(c); err == nil { |
| t.Errorf("Didn't reject config with invalid ReportingThreshold: 0") |
| } |
| } |
| |
| func TestValidateShuffledDpConfigWithInvalidPoissonMean(t *testing.T) { |
| invalidMeans := [4]float64{0, -1.0} |
| for _, pm := range invalidMeans { |
| c := &config.ReportDefinition_ShuffledDifferentialPrivacyConfig{ |
| Epsilon: 1, |
| Delta: 0.01, |
| ReportingThreshold: 10000, |
| PoissonMean: pm, |
| } |
| if err := validateShuffledDpConfig(c); err == nil { |
| t.Errorf("Didn't reject config with invalid PoissonMean: %f", pm) |
| } |
| } |
| } |
| |
| func TestValidateNewPrivacyFieldAndLegacyPrivacyFieldMatch(t *testing.T) { |
| // Default valid metric uses DEBUG max_release_stage. |
| r := makeValidReportWithType(config.ReportDefinition_UNIQUE_DEVICE_COUNTS) |
| |
| if err := validateNewPrivacyFieldAndLegacyPrivacyFieldMatch(r); err != nil { |
| t.Errorf("Rejected report with matched privacy_level NO_ADDED_PRIVACY and privacy_mechanism DE_IDENTIFICATION: %v", err) |
| } |
| |
| r.PrivacyLevel = config.ReportDefinition_LOW_PRIVACY |
| if err := validateNewPrivacyFieldAndLegacyPrivacyFieldMatch(r); err == nil { |
| t.Errorf("Didn't reject report with unmatched privacy_level LOW_PRIVACY and privacy_mechanism DE_IDENTIFICATION: %v", err) |
| } |
| |
| r.PrivacyLevel = config.ReportDefinition_MEDIUM_PRIVACY |
| if err := validateNewPrivacyFieldAndLegacyPrivacyFieldMatch(r); err == nil { |
| t.Errorf("Didn't reject report with unmatched privacy_level MEDIUM_PRIVACY and privacy_mechanism DE_IDENTIFICATION: %v", err) |
| } |
| |
| r.PrivacyLevel = config.ReportDefinition_HIGH_PRIVACY |
| if err := validateNewPrivacyFieldAndLegacyPrivacyFieldMatch(r); err == nil { |
| t.Errorf("Didn't reject report with unmatched privacy_level HIGH_PRIVACY and privacy_mechanism DE_IDENTIFICATION: %v", err) |
| } |
| |
| r.PrivacyMechanism = config.ReportDefinition_SHUFFLED_DIFFERENTIAL_PRIVACY |
| r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY |
| if err := validateNewPrivacyFieldAndLegacyPrivacyFieldMatch(r); err == nil { |
| t.Errorf("Didn't reject report with unmatched privacy_level NO_ADDED_PRIVACY and privacy_mechanism SHUFFLED_DIFFERENTIAL_PRIVACY: %v", err) |
| } |
| |
| r.PoissonMean = 1 |
| r.ReportingThreshold = 10000 |
| r.PrivacyConfig = &config.ReportDefinition_ShuffledDp{ |
| ShuffledDp: &config.ReportDefinition_ShuffledDifferentialPrivacyConfig{ |
| Epsilon: 1, |
| Delta: 0.001, |
| ReportingThreshold: 10000, |
| PoissonMean: 1, |
| }, |
| } |
| |
| r.PrivacyLevel = config.ReportDefinition_LOW_PRIVACY |
| if err := validateNewPrivacyFieldAndLegacyPrivacyFieldMatch(r); err != nil { |
| t.Errorf("Rejected report with matched privacy_level LOW_PRIVACY and privacy_mechanism SHUFFLED_DIFFERENTIAL_PRIVACY: %v", err) |
| } |
| |
| r.PrivacyLevel = config.ReportDefinition_MEDIUM_PRIVACY |
| if err := validateNewPrivacyFieldAndLegacyPrivacyFieldMatch(r); err != nil { |
| t.Errorf("Rejected report with matched privacy_level MEDIUM_PRIVACY and privacy_mechanism SHUFFLED_DIFFERENTIAL_PRIVACY: %v", err) |
| } |
| |
| r.PrivacyLevel = config.ReportDefinition_HIGH_PRIVACY |
| if err := validateNewPrivacyFieldAndLegacyPrivacyFieldMatch(r); err != nil { |
| t.Errorf("Rejected report with matched privacy_level HIGH_PRIVACY and privacy_mechanism SHUFFLED_DIFFERENTIAL_PRIVACY: %v", err) |
| } |
| |
| r.PoissonMean = 0.1 |
| if err := validateNewPrivacyFieldAndLegacyPrivacyFieldMatch(r); err == nil { |
| t.Errorf("Didn't Rejected report with unmatched poisson_mean and shuffled_dp possion_mean: %v", err) |
| } |
| |
| r.PoissonMean = 1 |
| r.ReportingThreshold = 1 |
| if err := validateNewPrivacyFieldAndLegacyPrivacyFieldMatch(r); err == nil { |
| t.Errorf("Didn't Rejected report with unmatched reporting_threshold and shuffled_dp reporting_threshold: %v", err) |
| } |
| |
| } |