// Copyright (c) Mainflux // SPDX-License-Identifier: Apache-2.0 package mocks import ( "sort" "strconv" "strings" "sync" "github.com/mainflux/mainflux/bootstrap" ) const ( emptyState = -1 notFoundIdx = -1 ) var _ bootstrap.ConfigRepository = (*configRepositoryMock)(nil) type configRepositoryMock struct { mu sync.Mutex counter uint64 configs map[string]bootstrap.Config channels map[string]bootstrap.Channel } // NewConfigsRepository creates in-memory config repository. func NewConfigsRepository() bootstrap.ConfigRepository { return &configRepositoryMock{ configs: make(map[string]bootstrap.Config), channels: make(map[string]bootstrap.Channel), } } func (crm *configRepositoryMock) Save(config bootstrap.Config, connections []string) (string, error) { crm.mu.Lock() defer crm.mu.Unlock() for _, v := range crm.configs { if v.MFThing == config.MFThing || v.ExternalID == config.ExternalID { return "", bootstrap.ErrConflict } } crm.counter++ config.MFThing = strconv.FormatUint(crm.counter, 10) crm.configs[config.MFThing] = config for _, ch := range config.MFChannels { crm.channels[ch.ID] = ch } config.MFChannels = []bootstrap.Channel{} for _, ch := range connections { config.MFChannels = append(config.MFChannels, crm.channels[ch]) } crm.configs[config.MFThing] = config return config.MFThing, nil } func (crm *configRepositoryMock) RetrieveByID(token, id string) (bootstrap.Config, error) { crm.mu.Lock() defer crm.mu.Unlock() c, ok := crm.configs[id] if !ok { return bootstrap.Config{}, bootstrap.ErrNotFound } if c.Owner != token { return bootstrap.Config{}, bootstrap.ErrUnauthorizedAccess } return c, nil } func (crm *configRepositoryMock) RetrieveAll(token string, filter bootstrap.Filter, offset, limit uint64) bootstrap.ConfigsPage { crm.mu.Lock() defer crm.mu.Unlock() configs := make([]bootstrap.Config, 0) if offset < 0 || limit <= 0 { return bootstrap.ConfigsPage{} } first := uint64(offset) + 1 last := first + uint64(limit) var state bootstrap.State = emptyState var name string if s, ok := filter.FullMatch["state"]; ok { val, _ := strconv.Atoi(s) state = bootstrap.State(val) } if s, ok := filter.PartialMatch["name"]; ok { name = strings.ToLower(s) } var total uint64 for _, v := range crm.configs { id, _ := strconv.ParseUint(v.MFThing, 10, 64) if (state == emptyState || v.State == state) && (name == "" || strings.Index(strings.ToLower(v.Name), name) != notFoundIdx) && v.Owner == token { if id >= first && id < last { configs = append(configs, v) } total++ } } sort.SliceStable(configs, func(i, j int) bool { return configs[i].MFThing < configs[j].MFThing }) return bootstrap.ConfigsPage{ Total: total, Offset: offset, Limit: limit, Configs: configs, } } func (crm *configRepositoryMock) RetrieveByExternalID(externalID string) (bootstrap.Config, error) { crm.mu.Lock() defer crm.mu.Unlock() for _, cfg := range crm.configs { if cfg.ExternalID == externalID { return cfg, nil } } return bootstrap.Config{}, bootstrap.ErrNotFound } func (crm *configRepositoryMock) Update(config bootstrap.Config) error { crm.mu.Lock() defer crm.mu.Unlock() cfg, ok := crm.configs[config.MFThing] if !ok || cfg.Owner != config.Owner { return bootstrap.ErrNotFound } cfg.Name = config.Name cfg.Content = config.Content crm.configs[config.MFThing] = cfg return nil } func (crm *configRepositoryMock) UpdateCert(owner, thingID, clientCert, clientKey, caCert string) error { crm.mu.Lock() defer crm.mu.Unlock() var forUpdate bootstrap.Config for _, v := range crm.configs { if v.MFThing == thingID && v.Owner == owner { forUpdate = v break } } if _, ok := crm.configs[forUpdate.MFThing]; !ok { return bootstrap.ErrNotFound } forUpdate.ClientCert = clientCert forUpdate.ClientKey = clientKey forUpdate.CACert = caCert crm.configs[forUpdate.MFThing] = forUpdate return nil } func (crm *configRepositoryMock) UpdateConnections(token, id string, channels []bootstrap.Channel, connections []string) error { crm.mu.Lock() defer crm.mu.Unlock() config, ok := crm.configs[id] if !ok { return bootstrap.ErrNotFound } for _, ch := range channels { crm.channels[ch.ID] = ch } config.MFChannels = []bootstrap.Channel{} for _, conn := range connections { ch, ok := crm.channels[conn] if !ok { return bootstrap.ErrNotFound } config.MFChannels = append(config.MFChannels, ch) } crm.configs[id] = config return nil } func (crm *configRepositoryMock) Remove(token, id string) error { crm.mu.Lock() defer crm.mu.Unlock() for k, v := range crm.configs { if v.Owner == token && k == id { delete(crm.configs, k) break } } return nil } func (crm *configRepositoryMock) ChangeState(token, id string, state bootstrap.State) error { crm.mu.Lock() defer crm.mu.Unlock() config, ok := crm.configs[id] if !ok { return bootstrap.ErrNotFound } if config.Owner != token { return bootstrap.ErrUnauthorizedAccess } config.State = state crm.configs[id] = config return nil } func (crm *configRepositoryMock) ListExisting(token string, connections []string) ([]bootstrap.Channel, error) { crm.mu.Lock() defer crm.mu.Unlock() var ret []bootstrap.Channel for k, v := range crm.channels { for _, conn := range connections { if conn == k { ret = append(ret, v) break } } } return ret, nil } func (crm *configRepositoryMock) RemoveThing(id string) error { crm.mu.Lock() defer crm.mu.Unlock() delete(crm.configs, id) return nil } func (crm *configRepositoryMock) UpdateChannel(ch bootstrap.Channel) error { crm.mu.Lock() defer crm.mu.Unlock() channel, ok := crm.channels[ch.ID] if !ok { return nil } channel.Name = ch.Name channel.Metadata = ch.Metadata crm.channels[ch.ID] = channel return nil } func (crm *configRepositoryMock) RemoveChannel(id string) error { crm.mu.Lock() defer crm.mu.Unlock() delete(crm.channels, id) return nil } func (crm *configRepositoryMock) DisconnectThing(channelID, thingID string) error { crm.mu.Lock() defer crm.mu.Unlock() idx := -1 if config, ok := crm.configs[thingID]; ok { for i, ch := range config.MFChannels { if ch.ID == channelID { idx = i break } } if idx != -1 { config.MFChannels = append(config.MFChannels[0:idx], config.MFChannels[idx:]...) } crm.configs[thingID] = config } delete(crm.channels, channelID) return nil }