package upstreamapi import ( "context" "testing" "git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/consts" "git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/model" "git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/model/entity" "git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/service" ) type channelTestConfigSource struct { service.IChannelConfig config *model.SimChannelConfigItemCache loadCall int } func (s *channelTestConfigSource) LoadChannelForTest(_ context.Context, channelID int64, tenantID string) (*model.SimChannelConfigItemCache, error) { s.loadCall++ if channelID != 42 || tenantID != "tenant-a" { return nil, model.ErrChannelNotAccessible } return s.config, nil } type requestScopedTestChannel struct { model.SimCardChannelI basicCalls int sessionCalls int } func (c *requestScopedTestChannel) QuerySimBasicInfo(_ context.Context, input *model.SimBasicInfoInput) (model.SimBasicInfoOutput, error) { c.basicCalls++ return model.SimBasicInfoOutput{&model.SimBaseRes[model.SimBasicInfoItem]{ Code: consts.ApiStatusSuccess, SimKey: input.SimKeys[0], ResData: model.SimBasicInfoItem{ ICCID: "iccid-complete", IMSI: "imsi-complete", MSISDN: "msisdn-complete", }, }}, nil } func (c *requestScopedTestChannel) QuerySimSession(_ context.Context, input *model.SimSessionInput) (model.SimSessionOutput, error) { c.sessionCalls++ return model.SimSessionOutput{&model.SimBaseRes[model.SimSessionItem]{ Code: consts.ApiStatusSuccess, SimKey: input.SimKeys[0], ResData: model.SimSessionItem{Status: "0"}, }}, nil } type requestScopedTestTemplate struct { calls int channel *requestScopedTestChannel } func (t *requestScopedTestTemplate) NewTemp(context.Context, *model.SimChannelConfigItemCache) model.SimCardChannelI { t.calls++ t.channel = &requestScopedTestChannel{} return t.channel } func TestChannelUsesOneTemporaryInstanceWithoutRuntimeRegistration(t *testing.T) { abnormal := 1 config := &model.SimChannelConfigItemCache{ Channel: &entity.TSimChannels{Id: 42, TenantId: "tenant-a", ChanKey: "abnormal-channel", TempKey: "test-template"}, Template: &entity.TSimChannelTemplate{TempKey: "test-template", TempModel: "direct"}, IsNormal: &abnormal, } source := &channelTestConfigSource{config: config} oldConfig := service.ChannelConfig() service.RegisterChannelConfig(source) t.Cleanup(func() { service.RegisterChannelConfig(oldConfig) }) api := NewUpstreamAPI() template := &requestScopedTestTemplate{} api.RegisterTemp("test-template", template) output, err := api.TestChannel(context.Background(), &model.ChannelTestInput{ ChannelID: 42, TenantID: "tenant-a", InputMode: model.ChannelTestInputModeSingle, CardValueType: "iccid", CardValue: "iccid-only", Items: []string{"simBasicInfo", "simSession"}, }) if err != nil { t.Fatal(err) } if source.loadCall != 1 || template.calls != 1 { t.Fatalf("config loads=%d instance builds=%d", source.loadCall, template.calls) } if template.channel.basicCalls != 2 || template.channel.sessionCalls != 1 { t.Fatalf("basic calls=%d session calls=%d", template.channel.basicCalls, template.channel.sessionCalls) } if len(output.Items) != 2 || output.Items[0].Status != "success" || output.Items[1].Status != "success" { t.Fatalf("unexpected test output: %+v", output) } if _, ok := api.GetChan("abnormal-channel"); ok { t.Fatal("temporary test instance entered the runtime channel map") } } func TestChannelPreservesExistingRuntimeInstance(t *testing.T) { normal := 0 config := &model.SimChannelConfigItemCache{ Channel: &entity.TSimChannels{Id: 42, TenantId: "tenant-a", ChanKey: "normal-channel", TempKey: "test-template"}, Template: &entity.TSimChannelTemplate{TempKey: "test-template", TempModel: "direct"}, IsNormal: &normal, } source := &channelTestConfigSource{config: config} oldConfig := service.ChannelConfig() service.RegisterChannelConfig(source) t.Cleanup(func() { service.RegisterChannelConfig(oldConfig) }) api := NewUpstreamAPI() runtimeInstance := &requestScopedTestChannel{} runtimeCache := &model.SimCardChannelCache{Instance: runtimeInstance, Version: "runtime-v1"} api.RegisterChan("normal-channel", runtimeCache.Version, runtimeCache) template := &requestScopedTestTemplate{} api.RegisterTemp("test-template", template) _, err := api.TestChannel(context.Background(), &model.ChannelTestInput{ ChannelID: 42, TenantID: "tenant-a", InputMode: model.ChannelTestInputModeComplete, ICCID: "i", IMSI: "m", MSISDN: "n", Items: []string{"simSession"}, }) if err != nil { t.Fatal(err) } got, ok := api.GetChan("normal-channel") if !ok || got != runtimeCache || got.Instance != runtimeInstance { t.Fatal("channel test replaced the existing runtime instance") } } func TestChannelRejectsTenantMismatchAndMutationItems(t *testing.T) { normal := 0 source := &channelTestConfigSource{config: &model.SimChannelConfigItemCache{ Channel: &entity.TSimChannels{Id: 42, TenantId: "tenant-a", ChanKey: "channel", TempKey: "test-template"}, Template: &entity.TSimChannelTemplate{TempKey: "test-template", TempModel: "direct"}, IsNormal: &normal, }} oldConfig := service.ChannelConfig() service.RegisterChannelConfig(source) t.Cleanup(func() { service.RegisterChannelConfig(oldConfig) }) api := NewUpstreamAPI() api.RegisterTemp("test-template", &requestScopedTestTemplate{}) if _, err := api.TestChannel(context.Background(), &model.ChannelTestInput{ChannelID: 42, TenantID: "tenant-b"}); err == nil || err.Error() != "channel is not accessible" { t.Fatalf("tenant mismatch error = %v", err) } if _, err := api.TestChannel(context.Background(), &model.ChannelTestInput{ ChannelID: 42, TenantID: "tenant-a", InputMode: model.ChannelTestInputModeComplete, ICCID: "i", IMSI: "m", MSISDN: "n", Items: []string{"changeSimStatus"}, }); err == nil { t.Fatal("mutation item was accepted") } } func TestUnregisterChanIsIdempotent(t *testing.T) { api := NewUpstreamAPI() api.UnregisterChan("missing") api.RegisterChan("channel", "v1", &model.SimCardChannelCache{Instance: &requestScopedTestChannel{}}) api.UnregisterChan("channel") api.UnregisterChan("channel") if _, ok := api.GetChan("channel"); ok { t.Fatal("channel remains registered") } }