From 8d817238e5ca441453e16745918827518d4a2214 Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 4 Oct 2023 09:42:17 +1300 Subject: [PATCH 1/4] taketwo --- migrations/back_37/back_37.go | 235 ++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 migrations/back_37/back_37.go diff --git a/migrations/back_37/back_37.go b/migrations/back_37/back_37.go new file mode 100644 index 000000000..c5a4a98c3 --- /dev/null +++ b/migrations/back_37/back_37.go @@ -0,0 +1,235 @@ +package main + +import ( + "context" + "fmt" + "strconv" + "time" + + "github.com/urfave/cli" + "go.mongodb.org/mongo-driver/bson" + + "github.com/tidepool-org/platform/application" + "github.com/tidepool-org/platform/data/blood/glucose" + "github.com/tidepool-org/platform/data/deduplicator/deduplicator" + "github.com/tidepool-org/platform/data/types" + "github.com/tidepool-org/platform/errors" + migrationMongo "github.com/tidepool-org/platform/migration/mongo" + storeStructuredMongo "github.com/tidepool-org/platform/store/structured/mongo" +) + +func main() { + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + application.RunAndExit(NewMigration(ctx)) +} + +type Migration struct { + ctx context.Context + *migrationMongo.Migration + dataRepository *storeStructuredMongo.Repository +} + +func createDatumHash(bsonData bson.M) (string, error) { + identityFields := []string{} + if bsonData["_userId"] == nil { + return "", errors.New("user id is missing") + } + userID := bsonData["_userId"].(string) + if userID == "" { + return "", errors.New("user id is empty") + } + identityFields = append(identityFields, userID) + if bsonData["deviceId"] == nil { + return "", errors.New("device id is missing") + } + deviceID := bsonData["deviceId"].(string) + if deviceID == "" { + return "", errors.New("device id is empty") + } + identityFields = append(identityFields, deviceID) + if bsonData["time"] == nil { + return "", errors.New("time is missing") + } + dataTime := bsonData["time"].(time.Time) + if dataTime.IsZero() { + return "", errors.New("time is empty") + } + identityFields = append(identityFields, dataTime.Format(types.TimeFormat)) + if bsonData["type"] == nil { + return "", errors.New("type is missing") + } + dataType := bsonData["type"].(string) + if dataType == "" { + return "", errors.New("type is empty") + } + identityFields = append(identityFields, dataType) + + switch dataType { + case "basal": + if bsonData["deliveryType"] == nil { + return "", errors.New("deliveryType is missing") + } + deliveryType := bsonData["deliveryType"].(string) + if deliveryType == "" { + return "", errors.New("deliveryType is empty") + } + identityFields = append(identityFields, deliveryType) + case "bolus", "deviceEvent": + if bsonData["subType"] == nil { + return "", errors.New("subType is missing") + } + subType := bsonData["subType"].(string) + if subType == "" { + return "", errors.New("subType is empty") + } + identityFields = append(identityFields, subType) + case "smbg", "bloodKetone", "cbg": + if bsonData["units"] == nil { + return "", errors.New("units is missing") + } + units := bsonData["units"].(string) + if units == "" { + return "", errors.New("units is empty") + } + identityFields = append(identityFields, units) + if bsonData["value"] == nil { + return "", errors.New("value is missing") + } + value := strconv.FormatFloat(bsonData["value"].(float64), 'f', -1, 64) + identityFields = append(identityFields, value) + } + return deduplicator.GenerateIdentityHash(identityFields) +} + +func NewMigration(ctx context.Context) *Migration { + return &Migration{ + ctx: ctx, + Migration: migrationMongo.NewMigration(), + } +} + +func (m *Migration) Initialize(provider application.Provider) error { + if err := m.Migration.Initialize(provider); err != nil { + return err + } + + m.CLI().Usage = "BACK-37: Migrate all existing data to add required Platform deduplication hash fields" + m.CLI().Description = "BACK-37: To fully migrate devices from the `jellyfish` upload API to the `platform` upload API" + m.CLI().Authors = []cli.Author{ + { + Name: "J H BATE", + Email: "jamie@tidepool.org", + }, + } + + m.CLI().Action = func(ctx *cli.Context) error { + if !m.ParseContext(ctx) { + return nil + } + return m.execute() + } + + return nil +} + +func (m *Migration) execute() error { + m.Logger().Debug("Migrate jellyfish API data") + m.Logger().Debug("Creating data store") + + mongoConfig := m.NewMongoConfig() + mongoConfig.Database = "data" + mongoConfig.Timeout = 60 * time.Minute + dataStore, err := storeStructuredMongo.NewStore(mongoConfig) + if err != nil { + return errors.Wrap(err, "unable to create data store") + } + defer dataStore.Terminate(m.ctx) + + m.Logger().Debug("Creating data repository") + + m.dataRepository = dataStore.GetRepository("deviceData") + m.Logger().Info("Migration of jellyfish documents has begun") + hashUpdatedCount, errorCount := m.migrateJellyfishDocuments() + m.Logger().Infof("Migrated %d jellyfish documents", hashUpdatedCount) + m.Logger().Infof("%d errors occurred", errorCount) + + return nil +} + +func (m *Migration) migrateJellyfishDocuments() (int, int) { + logger := m.Logger() + logger.Debug("Finding jellyfish data") + var hashUpdatedCount, errorCount int + selector := bson.M{ + // jellyfish uses a generated _id that is not an mongo objectId + "_id": bson.M{"$not": bson.M{"$type": "objectId"}}, + "_deduplicator": bson.M{"$exists": false}, + } + + var jellyfishResult bson.M + jellyfishDocCursor, err := m.dataRepository.Find(m.ctx, selector) + if err != nil { + logger.WithError(err).Error("Unable to find jellyfish data") + } + defer jellyfishDocCursor.Close(m.ctx) + for jellyfishDocCursor.Next(m.ctx) { + err = jellyfishDocCursor.Decode(&jellyfishResult) + if err != nil { + logger.WithError(err).Error("Could not decode mongo doc") + errorCount++ + continue + } + if !m.DryRun() { + if err := m.migrateDocument(jellyfishResult); err != nil { + logger.WithError(err).Errorf("Unable to migrate jellyfish document %s.", jellyfishResult["_id"]) + errorCount++ + continue + } + } + hashUpdatedCount++ + } + if err := jellyfishDocCursor.Err(); err != nil { + logger.WithError(err).Error("error while fetching data. Please re-run to complete the migration.") + errorCount++ + } + return hashUpdatedCount, errorCount +} + +func (m *Migration) migrateDocument(jfDatum bson.M) error { + var update bson.M + + switch jfDatum["type"] { + case "smbg", "bloodKetone", "cbg": + if len(fmt.Sprintf("%v", jfDatum["value"])) > 7 { + // NOTE: we need to ensure the same precision for the + // converted value as it is used to calculate the hash + val := jfDatum["value"].(float64) + mgdlVal := val*18.01559 + 0.5 + mgdL := glucose.MgdL + jfDatum["value"] = glucose.NormalizeValueForUnits(&mgdlVal, &mgdL) + hash, err := createDatumHash(jfDatum) + if err != nil { + return err + } + + update = bson.M{ + "$set": bson.M{ + "_deduplicator": bson.M{"hash": hash}, + "value": jfDatum["value"], + }, + } + } + default: + hash, err := createDatumHash(jfDatum) + if err != nil { + return err + } + update = bson.M{ + "$set": bson.M{"_deduplicator": bson.M{"hash": hash}}, + } + } + _, err := m.dataRepository.UpdateOne(m.ctx, bson.M{"_id": jfDatum["_id"], "modifiedTime": jfDatum["modifiedTime"]}, update) + return err +} From 315a2e8624d62c6308e9f361fc6b1fa6d6b0b54f Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 4 Oct 2023 15:25:30 +1300 Subject: [PATCH 2/4] cleanup based on disscusion --- migrations/back_37/back_37.go | 183 ++++++++++++++++++---------------- 1 file changed, 96 insertions(+), 87 deletions(-) diff --git a/migrations/back_37/back_37.go b/migrations/back_37/back_37.go index c5a4a98c3..8494ff0c0 100644 --- a/migrations/back_37/back_37.go +++ b/migrations/back_37/back_37.go @@ -31,74 +31,91 @@ type Migration struct { dataRepository *storeStructuredMongo.Repository } -func createDatumHash(bsonData bson.M) (string, error) { - identityFields := []string{} - if bsonData["_userId"] == nil { - return "", errors.New("user id is missing") - } - userID := bsonData["_userId"].(string) - if userID == "" { - return "", errors.New("user id is empty") - } - identityFields = append(identityFields, userID) - if bsonData["deviceId"] == nil { - return "", errors.New("device id is missing") - } - deviceID := bsonData["deviceId"].(string) - if deviceID == "" { - return "", errors.New("device id is empty") - } - identityFields = append(identityFields, deviceID) - if bsonData["time"] == nil { - return "", errors.New("time is missing") +func getValidatedString(bsonData bson.M, fieldName string) (string, error) { + if valRaw, ok := bsonData[fieldName]; !ok { + return "", errors.Newf("%s is missing", fieldName) + } else if val, ok := valRaw.(string); !ok { + return "", errors.Newf("%s is not of expected type", fieldName) + } else if val == "" { + return "", errors.Newf("%s is empty", fieldName) + } else { + return val, nil } - dataTime := bsonData["time"].(time.Time) - if dataTime.IsZero() { - return "", errors.New("time is empty") - } - identityFields = append(identityFields, dataTime.Format(types.TimeFormat)) - if bsonData["type"] == nil { - return "", errors.New("type is missing") +} + +func getValidatedTime(bsonData bson.M, fieldName string) (time.Time, error) { + if valRaw, ok := bsonData[fieldName]; !ok { + return time.Time{}, errors.Newf("%s is missing", fieldName) + } else if val, ok := valRaw.(time.Time); !ok { + return time.Time{}, errors.Newf("%s is not of expected type", fieldName) + } else if val.IsZero() { + return time.Time{}, errors.Newf("%s is empty", fieldName) + } else { + return val, nil } - dataType := bsonData["type"].(string) - if dataType == "" { - return "", errors.New("type is empty") +} + +func createDatumHash(bsonData bson.M) (string, error) { + identityFields := []string{} + if userID, err := getValidatedString(bsonData, "_userId"); err != nil { + return "", err + } else { + identityFields = append(identityFields, userID) + } + if deviceID, err := getValidatedString(bsonData, "deviceId"); err != nil { + return "", err + } else { + identityFields = append(identityFields, deviceID) + } + if datumTime, err := getValidatedTime(bsonData, "time"); err != nil { + return "", err + } else { + identityFields = append(identityFields, datumTime.Format(types.TimeFormat)) + } + datumType, err := getValidatedString(bsonData, "type") + if err != nil { + return "", err } - identityFields = append(identityFields, dataType) + identityFields = append(identityFields, datumType) - switch dataType { + switch datumType { case "basal": - if bsonData["deliveryType"] == nil { - return "", errors.New("deliveryType is missing") - } - deliveryType := bsonData["deliveryType"].(string) - if deliveryType == "" { - return "", errors.New("deliveryType is empty") + if deliveryType, err := getValidatedString(bsonData, "deliveryType"); err != nil { + return "", err + } else { + identityFields = append(identityFields, deliveryType) } - identityFields = append(identityFields, deliveryType) case "bolus", "deviceEvent": - if bsonData["subType"] == nil { - return "", errors.New("subType is missing") + if subType, err := getValidatedString(bsonData, "subType"); err != nil { + return "", err + } else { + identityFields = append(identityFields, subType) } - subType := bsonData["subType"].(string) - if subType == "" { - return "", errors.New("subType is empty") - } - identityFields = append(identityFields, subType) case "smbg", "bloodKetone", "cbg": - if bsonData["units"] == nil { - return "", errors.New("units is missing") - } - units := bsonData["units"].(string) - if units == "" { - return "", errors.New("units is empty") + if units, err := getValidatedString(bsonData, "units"); err != nil { + return "", err + } else { + identityFields = append(identityFields, units) } - identityFields = append(identityFields, units) - if bsonData["value"] == nil { + + valueRaw, ok := bsonData["value"] + if !ok { return "", errors.New("value is missing") } - value := strconv.FormatFloat(bsonData["value"].(float64), 'f', -1, 64) - identityFields = append(identityFields, value) + val, ok := valueRaw.(float64) + if !ok { + return "", errors.New("value is not of expected type") + } + + if len(fmt.Sprintf("%v", valueRaw)) > 7 { + // NOTE: we need to ensure the same precision for the + // converted value as it is used to calculate the hash + mgdlVal := val*18.01559 + 0.5 + mgdL := glucose.MgdL + val = *glucose.NormalizeValueForUnits(&mgdlVal, &mgdL) + } + strVal := strconv.FormatFloat(val, 'f', -1, 64) + identityFields = append(identityFields, strVal) } return deduplicator.GenerateIdentityHash(identityFields) } @@ -148,7 +165,6 @@ func (m *Migration) execute() error { defer dataStore.Terminate(m.ctx) m.Logger().Debug("Creating data repository") - m.dataRepository = dataStore.GetRepository("deviceData") m.Logger().Info("Migration of jellyfish documents has begun") hashUpdatedCount, errorCount := m.migrateJellyfishDocuments() @@ -198,38 +214,31 @@ func (m *Migration) migrateJellyfishDocuments() (int, int) { } func (m *Migration) migrateDocument(jfDatum bson.M) error { - var update bson.M - switch jfDatum["type"] { - case "smbg", "bloodKetone", "cbg": - if len(fmt.Sprintf("%v", jfDatum["value"])) > 7 { - // NOTE: we need to ensure the same precision for the - // converted value as it is used to calculate the hash - val := jfDatum["value"].(float64) - mgdlVal := val*18.01559 + 0.5 - mgdL := glucose.MgdL - jfDatum["value"] = glucose.NormalizeValueForUnits(&mgdlVal, &mgdL) - hash, err := createDatumHash(jfDatum) - if err != nil { - return err - } + datumID, err := getValidatedString(jfDatum, "_id") + if err != nil { + return err + } - update = bson.M{ - "$set": bson.M{ - "_deduplicator": bson.M{"hash": hash}, - "value": jfDatum["value"], - }, - } - } - default: - hash, err := createDatumHash(jfDatum) - if err != nil { - return err - } - update = bson.M{ - "$set": bson.M{"_deduplicator": bson.M{"hash": hash}}, - } + var modifiedTime *time.Time + if timeRaw, ok := jfDatum["modifiedTime"]; !ok { + modifiedTime = nil + } else if val, ok := timeRaw.(time.Time); !ok { + modifiedTime = nil + } else { + modifiedTime = &val + } + + hash, err := createDatumHash(jfDatum) + if err != nil { + return err + } + update := bson.M{ + "$set": bson.M{"_deduplicator": bson.M{"hash": hash}}, } - _, err := m.dataRepository.UpdateOne(m.ctx, bson.M{"_id": jfDatum["_id"], "modifiedTime": jfDatum["modifiedTime"]}, update) + _, err = m.dataRepository.UpdateOne(m.ctx, bson.M{ + "_id": datumID, + "modifiedTime": modifiedTime, + }, update) return err } From 68caf75762c5efc7ad589910e5a50eb7b0c7dfd8 Mon Sep 17 00:00:00 2001 From: Jamie Date: Thu, 5 Oct 2023 16:51:04 +1300 Subject: [PATCH 3/4] review updates --- migrations/back_37/back_37.go | 87 ++++++++++++++++-------------- migrations/back_37/back_37_test.go | 40 ++++++++++++++ 2 files changed, 88 insertions(+), 39 deletions(-) create mode 100644 migrations/back_37/back_37_test.go diff --git a/migrations/back_37/back_37.go b/migrations/back_37/back_37.go index 8494ff0c0..650bbaec3 100644 --- a/migrations/back_37/back_37.go +++ b/migrations/back_37/back_37.go @@ -13,6 +13,12 @@ import ( "github.com/tidepool-org/platform/data/blood/glucose" "github.com/tidepool-org/platform/data/deduplicator/deduplicator" "github.com/tidepool-org/platform/data/types" + "github.com/tidepool-org/platform/data/types/basal" + "github.com/tidepool-org/platform/data/types/blood/glucose/continuous" + "github.com/tidepool-org/platform/data/types/blood/glucose/selfmonitored" + "github.com/tidepool-org/platform/data/types/blood/ketone" + "github.com/tidepool-org/platform/data/types/bolus" + "github.com/tidepool-org/platform/data/types/device" "github.com/tidepool-org/platform/errors" migrationMongo "github.com/tidepool-org/platform/migration/mongo" storeStructuredMongo "github.com/tidepool-org/platform/store/structured/mongo" @@ -57,10 +63,10 @@ func getValidatedTime(bsonData bson.M, fieldName string) (time.Time, error) { func createDatumHash(bsonData bson.M) (string, error) { identityFields := []string{} - if userID, err := getValidatedString(bsonData, "_userId"); err != nil { + if datumUserID, err := getValidatedString(bsonData, "_userId"); err != nil { return "", err } else { - identityFields = append(identityFields, userID) + identityFields = append(identityFields, datumUserID) } if deviceID, err := getValidatedString(bsonData, "deviceId"); err != nil { return "", err @@ -79,47 +85,51 @@ func createDatumHash(bsonData bson.M) (string, error) { identityFields = append(identityFields, datumType) switch datumType { - case "basal": + case basal.Type: if deliveryType, err := getValidatedString(bsonData, "deliveryType"); err != nil { return "", err } else { identityFields = append(identityFields, deliveryType) } - case "bolus", "deviceEvent": + case bolus.Type, device.Type: if subType, err := getValidatedString(bsonData, "subType"); err != nil { return "", err } else { identityFields = append(identityFields, subType) } - case "smbg", "bloodKetone", "cbg": - if units, err := getValidatedString(bsonData, "units"); err != nil { + case selfmonitored.Type, ketone.Type, continuous.Type: + units, err := getValidatedString(bsonData, "units") + if err != nil { return "", err } else { identityFields = append(identityFields, units) } - valueRaw, ok := bsonData["value"] - if !ok { + if valueRaw, ok := bsonData["value"]; !ok { return "", errors.New("value is missing") - } - val, ok := valueRaw.(float64) - if !ok { + } else if val, ok := valueRaw.(float64); !ok { return "", errors.New("value is not of expected type") + } else { + if units != glucose.MgdL && units != glucose.Mgdl { + // NOTE: we need to ensure the same precision for the + // converted value as it is used to calculate the hash + val = getBGValuePlatformPrecision(val) + } + identityFields = append(identityFields, strconv.FormatFloat(val, 'f', -1, 64)) } - - if len(fmt.Sprintf("%v", valueRaw)) > 7 { - // NOTE: we need to ensure the same precision for the - // converted value as it is used to calculate the hash - mgdlVal := val*18.01559 + 0.5 - mgdL := glucose.MgdL - val = *glucose.NormalizeValueForUnits(&mgdlVal, &mgdL) - } - strVal := strconv.FormatFloat(val, 'f', -1, 64) - identityFields = append(identityFields, strVal) } return deduplicator.GenerateIdentityHash(identityFields) } +func getBGValuePlatformPrecision(mmolVal float64) float64 { + if len(fmt.Sprintf("%v", mmolVal)) > 7 { + mgdlVal := mmolVal * glucose.MmolLToMgdLConversionFactor + mgdL := glucose.MgdL + mmolVal = *glucose.NormalizeValueForUnits(&mgdlVal, &mgdL) + } + return mmolVal +} + func NewMigration(ctx context.Context) *Migration { return &Migration{ ctx: ctx, @@ -188,6 +198,8 @@ func (m *Migration) migrateJellyfishDocuments() (int, int) { jellyfishDocCursor, err := m.dataRepository.Find(m.ctx, selector) if err != nil { logger.WithError(err).Error("Unable to find jellyfish data") + errorCount++ + return hashUpdatedCount, errorCount } defer jellyfishDocCursor.Close(m.ctx) for jellyfishDocCursor.Next(m.ctx) { @@ -198,47 +210,44 @@ func (m *Migration) migrateJellyfishDocuments() (int, int) { continue } if !m.DryRun() { - if err := m.migrateDocument(jellyfishResult); err != nil { + if updated, err := m.migrateDocument(jellyfishResult); err != nil { logger.WithError(err).Errorf("Unable to migrate jellyfish document %s.", jellyfishResult["_id"]) errorCount++ continue + } else if updated { + hashUpdatedCount++ } } - hashUpdatedCount++ } if err := jellyfishDocCursor.Err(); err != nil { - logger.WithError(err).Error("error while fetching data. Please re-run to complete the migration.") + logger.WithError(err).Error("Error while fetching data. Please re-run to complete the migration.") errorCount++ } return hashUpdatedCount, errorCount } -func (m *Migration) migrateDocument(jfDatum bson.M) error { +func (m *Migration) migrateDocument(jfDatum bson.M) (bool, error) { datumID, err := getValidatedString(jfDatum, "_id") if err != nil { - return err - } - - var modifiedTime *time.Time - if timeRaw, ok := jfDatum["modifiedTime"]; !ok { - modifiedTime = nil - } else if val, ok := timeRaw.(time.Time); !ok { - modifiedTime = nil - } else { - modifiedTime = &val + return false, err } hash, err := createDatumHash(jfDatum) if err != nil { - return err + return false, err } update := bson.M{ "$set": bson.M{"_deduplicator": bson.M{"hash": hash}}, } - _, err = m.dataRepository.UpdateOne(m.ctx, bson.M{ + result, err := m.dataRepository.UpdateOne(m.ctx, bson.M{ "_id": datumID, - "modifiedTime": modifiedTime, + "modifiedTime": jfDatum["modifiedTime"], }, update) - return err + + if err != nil { + return false, err + } + + return result.ModifiedCount == 1, nil } diff --git a/migrations/back_37/back_37_test.go b/migrations/back_37/back_37_test.go new file mode 100644 index 000000000..6883beb23 --- /dev/null +++ b/migrations/back_37/back_37_test.go @@ -0,0 +1,40 @@ +package main + +import "testing" + +func Test_getBGValuePlatformPrecision(t *testing.T) { + + tests := []struct { + name string + mmolJellyfishVal float64 + mmolPlatformVal float64 + }{ + { + name: "original mmol/L value", + mmolJellyfishVal: 10.1, + mmolPlatformVal: 10.1, + }, + { + name: "converted mgd/L of 100", + mmolJellyfishVal: 5.550747991045533, + mmolPlatformVal: 5.55075, + }, + { + name: "converted mgd/L of 150.0", + mmolJellyfishVal: 8.3261219865683, + mmolPlatformVal: 8.32612, + }, + { + name: "converted mgd/L of 65.0", + mmolJellyfishVal: 3.6079861941795968, + mmolPlatformVal: 3.60799, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getBGValuePlatformPrecision(tt.mmolJellyfishVal); got != tt.mmolPlatformVal { + t.Errorf("getBGValuePlatformPrecision() mmolJellyfishVal = %v, want %v", got, tt.mmolPlatformVal) + } + }) + } +} From 08387b42910cdf28d8d03bfdf675d57e816f216b Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 8 Nov 2023 15:25:04 +1300 Subject: [PATCH 4/4] migrate jellyfish pumpSettings bolus if present --- migrations/back_37/back_37.go | 32 +++++++++++-- migrations/back_37/back_37_test.go | 72 +++++++++++++++++++++++++++++- 2 files changed, 100 insertions(+), 4 deletions(-) diff --git a/migrations/back_37/back_37.go b/migrations/back_37/back_37.go index 650bbaec3..cc65cc128 100644 --- a/migrations/back_37/back_37.go +++ b/migrations/back_37/back_37.go @@ -121,6 +121,23 @@ func createDatumHash(bsonData bson.M) (string, error) { return deduplicator.GenerateIdentityHash(identityFields) } +func updateIfExistsPumpSettingsBolus(bsonData bson.M) (interface{}, error) { + dataType, err := getValidatedString(bsonData, "type") + if err != nil { + return nil, err + } + if dataType == "pumpSettings" { + if bolus := bsonData["bolus"]; bolus != nil { + boluses, ok := bolus.(map[string]interface{}) + if !ok { + return nil, errors.Newf("pumpSettings.bolus is not the expected type %v", bolus) + } + return boluses, nil + } + } + return nil, nil +} + func getBGValuePlatformPrecision(mmolVal float64) float64 { if len(fmt.Sprintf("%v", mmolVal)) > 7 { mgdlVal := mmolVal * glucose.MmolLToMgdLConversionFactor @@ -233,17 +250,26 @@ func (m *Migration) migrateDocument(jfDatum bson.M) (bool, error) { return false, err } + updates := bson.M{} hash, err := createDatumHash(jfDatum) if err != nil { return false, err } - update := bson.M{ - "$set": bson.M{"_deduplicator": bson.M{"hash": hash}}, + + updates["_deduplicator"] = bson.M{"hash": hash} + + if boluses, err := updateIfExistsPumpSettingsBolus(jfDatum); err != nil { + return false, err + } else if boluses != nil { + updates["pumpSettings"] = bson.M{"boluses": boluses} } + result, err := m.dataRepository.UpdateOne(m.ctx, bson.M{ "_id": datumID, "modifiedTime": jfDatum["modifiedTime"], - }, update) + }, bson.M{ + "$set": updates, + }) if err != nil { return false, err diff --git a/migrations/back_37/back_37_test.go b/migrations/back_37/back_37_test.go index 6883beb23..3d3ccc63b 100644 --- a/migrations/back_37/back_37_test.go +++ b/migrations/back_37/back_37_test.go @@ -1,6 +1,13 @@ package main -import "testing" +import ( + "reflect" + "testing" + + "go.mongodb.org/mongo-driver/bson" + + pumpTest "github.com/tidepool-org/platform/data/types/settings/pump/test" +) func Test_getBGValuePlatformPrecision(t *testing.T) { @@ -38,3 +45,66 @@ func Test_getBGValuePlatformPrecision(t *testing.T) { }) } } + +func Test_updateIfExistsPumpSettingsBolus(t *testing.T) { + type args struct { + bsonData bson.M + } + + bolusData := map[string]interface{}{ + "bolous-1": pumpTest.NewBolus(), + "bolous-2": pumpTest.NewBolus(), + } + + tests := []struct { + name string + args args + want interface{} + wantErr bool + }{ + { + name: "when not pumpSettings", + args: args{ + bsonData: bson.M{"type": "other"}, + }, + want: nil, + wantErr: false, + }, + { + name: "pumpSettings but no bolus", + args: args{ + bsonData: bson.M{"type": "pumpSettings"}, + }, + want: nil, + wantErr: false, + }, + { + name: "pumpSettings bolus wrong type", + args: args{ + bsonData: bson.M{"type": "pumpSettings", "bolus": "wrong"}, + }, + want: nil, + wantErr: true, + }, + { + name: "pumpSettings bolus valid type", + args: args{ + bsonData: bson.M{"type": "pumpSettings", "bolus": bolusData}, + }, + want: bolusData, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := updateIfExistsPumpSettingsBolus(tt.args.bsonData) + if (err != nil) != tt.wantErr { + t.Errorf("updateIfExistsPumpSettingsBolus() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("updateIfExistsPumpSettingsBolus() = %v, want %v", got, tt.want) + } + }) + } +}