add social network test
This commit is contained in:
parent
647e645d61
commit
184586cdff
2
Makefile
2
Makefile
|
@ -63,6 +63,8 @@ mock-gen: # 建立 mock 資料
|
||||||
mockgen -source=./internal/model/mongo/post_model.go -destination=./internal/mock/model/post_model.go -package=mock
|
mockgen -source=./internal/model/mongo/post_model.go -destination=./internal/mock/model/post_model.go -package=mock
|
||||||
mockgen -source=./internal/model/mongo/comment_model_gen.go -destination=./internal/mock/model/comment_model_gen.go -package=mock
|
mockgen -source=./internal/model/mongo/comment_model_gen.go -destination=./internal/mock/model/comment_model_gen.go -package=mock
|
||||||
mockgen -source=./internal/model/mongo/comment_model.go -destination=./internal/mock/model/comment_model.go -package=mock
|
mockgen -source=./internal/model/mongo/comment_model.go -destination=./internal/mock/model/comment_model.go -package=mock
|
||||||
|
mockgen -source=./internal/domain/repository/social_network.go -destination=./internal/mock/repository/social_network.go -package=mock
|
||||||
|
mockgen -source=./internal/domain/repository/timeline.go -destination=./internal/mock/repository/timeline.go -package=mock
|
||||||
@echo "Generate mock files successfully"
|
@echo "Generate mock files successfully"
|
||||||
|
|
||||||
.PHONY: migrate-database
|
.PHONY: migrate-database
|
||||||
|
|
33
go.mod
33
go.mod
|
@ -8,6 +8,7 @@ require (
|
||||||
github.com/alicebob/miniredis/v2 v2.33.0
|
github.com/alicebob/miniredis/v2 v2.33.0
|
||||||
github.com/neo4j/neo4j-go-driver/v5 v5.24.0
|
github.com/neo4j/neo4j-go-driver/v5 v5.24.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
|
github.com/testcontainers/testcontainers-go v0.33.0
|
||||||
github.com/zeromicro/go-zero v1.7.0
|
github.com/zeromicro/go-zero v1.7.0
|
||||||
go.mongodb.org/mongo-driver v1.16.0
|
go.mongodb.org/mongo-driver v1.16.0
|
||||||
go.uber.org/mock v0.4.0
|
go.uber.org/mock v0.4.0
|
||||||
|
@ -16,19 +17,32 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
dario.cat/mergo v1.0.0 // indirect
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
|
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/containerd/containerd v1.7.18 // indirect
|
||||||
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
|
github.com/containerd/platforms v0.2.1 // indirect
|
||||||
github.com/coreos/go-semver v0.3.1 // indirect
|
github.com/coreos/go-semver v0.3.1 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
|
github.com/docker/docker v27.1.1+incompatible // indirect
|
||||||
|
github.com/docker/go-connections v0.5.0 // indirect
|
||||||
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||||
github.com/fatih/color v1.17.0 // indirect
|
github.com/fatih/color v1.17.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||||
github.com/go-openapi/swag v0.22.4 // indirect
|
github.com/go-openapi/swag v0.22.4 // indirect
|
||||||
|
@ -48,30 +62,49 @@ require (
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.8 // indirect
|
github.com/klauspost/compress v1.17.8 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
|
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||||
|
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||||
|
github.com/moby/sys/user v0.1.0 // indirect
|
||||||
|
github.com/moby/term v0.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||||
github.com/openzipkin/zipkin-go v0.4.3 // indirect
|
github.com/openzipkin/zipkin-go v0.4.3 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||||
github.com/prometheus/client_model v0.5.0 // indirect
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
github.com/prometheus/common v0.48.0 // indirect
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.6.1 // indirect
|
github.com/redis/go-redis/v9 v9.6.1 // indirect
|
||||||
|
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
github.com/xdg-go/scram v1.1.2 // indirect
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
||||||
github.com/yuin/gopher-lua v1.1.1 // indirect
|
github.com/yuin/gopher-lua v1.1.1 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
|
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
|
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
|
||||||
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
|
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
|
||||||
|
|
|
@ -27,7 +27,7 @@ type Config struct {
|
||||||
// Redis Cluster
|
// Redis Cluster
|
||||||
RedisCluster redis.RedisConf
|
RedisCluster redis.RedisConf
|
||||||
|
|
||||||
// 圖形話資料庫
|
// 圖形資料庫
|
||||||
Neo4J struct {
|
Neo4J struct {
|
||||||
URI string
|
URI string
|
||||||
Username string
|
Username string
|
||||||
|
|
|
@ -0,0 +1,209 @@
|
||||||
|
package neo4j
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/testcontainers/testcontainers-go"
|
||||||
|
"github.com/testcontainers/testcontainers-go/wait"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewNeo4J(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
conf *Config
|
||||||
|
expected *Config
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid configuration",
|
||||||
|
conf: &Config{
|
||||||
|
URI: "neo4j://localhost:7687",
|
||||||
|
Username: "neo4j",
|
||||||
|
Password: "password",
|
||||||
|
LogLevel: "info",
|
||||||
|
MaxConnectionLifetime: time.Minute * 5,
|
||||||
|
MaxConnectionPoolSize: 10,
|
||||||
|
ConnectionTimeout: time.Second * 5,
|
||||||
|
},
|
||||||
|
expected: &Config{
|
||||||
|
URI: "neo4j://localhost:7687",
|
||||||
|
Username: "neo4j",
|
||||||
|
Password: "password",
|
||||||
|
LogLevel: "info",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty URI",
|
||||||
|
conf: &Config{
|
||||||
|
URI: "",
|
||||||
|
Username: "neo4j",
|
||||||
|
Password: "password",
|
||||||
|
LogLevel: "info",
|
||||||
|
MaxConnectionLifetime: time.Minute * 5,
|
||||||
|
MaxConnectionPoolSize: 10,
|
||||||
|
ConnectionTimeout: time.Second * 5,
|
||||||
|
},
|
||||||
|
expected: &Config{
|
||||||
|
URI: "",
|
||||||
|
Username: "neo4j",
|
||||||
|
Password: "password",
|
||||||
|
LogLevel: "info",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty username and password",
|
||||||
|
conf: &Config{
|
||||||
|
URI: "neo4j://localhost:7687",
|
||||||
|
Username: "",
|
||||||
|
Password: "",
|
||||||
|
LogLevel: "info",
|
||||||
|
MaxConnectionLifetime: time.Minute * 5,
|
||||||
|
MaxConnectionPoolSize: 10,
|
||||||
|
ConnectionTimeout: time.Second * 5,
|
||||||
|
},
|
||||||
|
expected: &Config{
|
||||||
|
URI: "neo4j://localhost:7687",
|
||||||
|
Username: "",
|
||||||
|
Password: "",
|
||||||
|
LogLevel: "info",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom log level",
|
||||||
|
conf: &Config{
|
||||||
|
URI: "neo4j://localhost:7687",
|
||||||
|
Username: "neo4j",
|
||||||
|
Password: "password",
|
||||||
|
LogLevel: "debug",
|
||||||
|
MaxConnectionLifetime: time.Minute * 5,
|
||||||
|
MaxConnectionPoolSize: 10,
|
||||||
|
ConnectionTimeout: time.Second * 5,
|
||||||
|
},
|
||||||
|
expected: &Config{
|
||||||
|
URI: "neo4j://localhost:7687",
|
||||||
|
Username: "neo4j",
|
||||||
|
Password: "password",
|
||||||
|
LogLevel: "debug",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
client := NewNeo4J(tt.conf)
|
||||||
|
assert.NotNil(t, client)
|
||||||
|
assert.Equal(t, tt.expected.URI, client.serviceConf.URI)
|
||||||
|
assert.Equal(t, tt.expected.Username, client.serviceConf.Username)
|
||||||
|
assert.Equal(t, tt.expected.Password, client.serviceConf.Password)
|
||||||
|
assert.Equal(t, tt.expected.LogLevel, client.serviceConf.LogLevel)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConn(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
neo4jContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||||
|
ContainerRequest: testcontainers.ContainerRequest{
|
||||||
|
Image: "neo4j:latest",
|
||||||
|
ExposedPorts: []string{"7687/tcp"},
|
||||||
|
Env: map[string]string{
|
||||||
|
"NEO4J_AUTH": "neo4j/yyyytttt",
|
||||||
|
},
|
||||||
|
WaitingFor: wait.ForLog("Started"),
|
||||||
|
},
|
||||||
|
Started: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer neo4jContainer.Terminate(ctx)
|
||||||
|
|
||||||
|
host, _ := neo4jContainer.Host(ctx)
|
||||||
|
port, _ := neo4jContainer.MappedPort(ctx, "7687")
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("bolt://%s:%s", host, port.Port())
|
||||||
|
t.Log("Neo4j running at:", uri)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
conf *Config
|
||||||
|
shouldFail bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "successful connection",
|
||||||
|
conf: &Config{
|
||||||
|
URI: uri,
|
||||||
|
Username: "neo4j",
|
||||||
|
Password: "yyyytttt",
|
||||||
|
LogLevel: "info",
|
||||||
|
MaxConnectionLifetime: time.Minute * 5,
|
||||||
|
MaxConnectionPoolSize: 10,
|
||||||
|
ConnectionTimeout: time.Second * 5,
|
||||||
|
},
|
||||||
|
shouldFail: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failed connection due to invalid URI",
|
||||||
|
conf: &Config{
|
||||||
|
URI: uri,
|
||||||
|
Username: "neo4j",
|
||||||
|
Password: "wrongpassword",
|
||||||
|
LogLevel: "info",
|
||||||
|
MaxConnectionLifetime: time.Minute * 5,
|
||||||
|
MaxConnectionPoolSize: 10,
|
||||||
|
ConnectionTimeout: time.Second * 5,
|
||||||
|
},
|
||||||
|
shouldFail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failed connection due to missing URI",
|
||||||
|
conf: &Config{
|
||||||
|
URI: "",
|
||||||
|
Username: "neo4j",
|
||||||
|
Password: "password",
|
||||||
|
LogLevel: "info",
|
||||||
|
MaxConnectionLifetime: time.Minute * 5,
|
||||||
|
MaxConnectionPoolSize: 10,
|
||||||
|
ConnectionTimeout: time.Second * 5,
|
||||||
|
},
|
||||||
|
shouldFail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failed connection due to missing username and password",
|
||||||
|
conf: &Config{
|
||||||
|
URI: uri,
|
||||||
|
Username: "",
|
||||||
|
Password: "",
|
||||||
|
LogLevel: "info",
|
||||||
|
MaxConnectionLifetime: time.Minute * 5,
|
||||||
|
MaxConnectionPoolSize: 10,
|
||||||
|
ConnectionTimeout: time.Second * 5,
|
||||||
|
},
|
||||||
|
shouldFail: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
client := NewNeo4J(tt.conf)
|
||||||
|
driver, err := client.Conn()
|
||||||
|
if tt.shouldFail {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, driver)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, driver)
|
||||||
|
|
||||||
|
// Close the driver after test
|
||||||
|
defer func() {
|
||||||
|
err := driver.Close(context.Background())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package socialnetworkservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||||
|
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetFolloweeCountLogic_GetFolloweeCount(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
||||||
|
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||||
|
|
||||||
|
svcCtx := &svc.ServiceContext{
|
||||||
|
SocialNetworkRepository: mockSocialNetworkRepository,
|
||||||
|
Validate: mockValidate,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试数据集
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input *tweeting.FollowCountReq
|
||||||
|
prepare func()
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ok",
|
||||||
|
input: &tweeting.FollowCountReq{
|
||||||
|
Uid: "12345",
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||||
|
mockSocialNetworkRepository.EXPECT().GetFolloweeCount(gomock.Any(), "12345").Return(int64(10), nil).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "驗證失敗",
|
||||||
|
input: &tweeting.FollowCountReq{
|
||||||
|
Uid: "",
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "取得跟隨數量失敗",
|
||||||
|
input: &tweeting.FollowCountReq{
|
||||||
|
Uid: "12345",
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||||
|
mockSocialNetworkRepository.EXPECT().GetFolloweeCount(gomock.Any(), "12345").Return(int64(0), errors.New("repository error")).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.prepare()
|
||||||
|
|
||||||
|
logic := GetFolloweeCountLogic{
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
ctx: context.TODO(),
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := logic.GetFolloweeCount(tt.input)
|
||||||
|
|
||||||
|
if tt.expectErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.input.Uid, got.Uid)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package socialnetworkservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
"app-cloudep-tweeting-service/internal/domain/repository"
|
||||||
|
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||||
|
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetFolloweeLogic_GetFollowee(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
// 初始化 mock 依賴
|
||||||
|
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
||||||
|
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||||
|
|
||||||
|
// 初始化服務上下文
|
||||||
|
svcCtx := &svc.ServiceContext{
|
||||||
|
SocialNetworkRepository: mockSocialNetworkRepository,
|
||||||
|
Validate: mockValidate,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試數據集
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input *tweeting.FollowReq
|
||||||
|
prepare func()
|
||||||
|
expectErr bool
|
||||||
|
wantResp *tweeting.FollowResp
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功獲取我跟隨的名單",
|
||||||
|
input: &tweeting.FollowReq{
|
||||||
|
Uid: "12345",
|
||||||
|
PageSize: 10,
|
||||||
|
PageIndex: 1,
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
// 模擬驗證通過
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||||
|
// 模擬 GetFollowee 返回正確的結果
|
||||||
|
mockSocialNetworkRepository.EXPECT().GetFollowee(gomock.Any(), repository.FollowReq{
|
||||||
|
UID: "12345",
|
||||||
|
PageSize: 10,
|
||||||
|
PageIndex: 1,
|
||||||
|
}).Return(repository.FollowResp{
|
||||||
|
UIDs: []string{"user1", "user2"},
|
||||||
|
Total: 2,
|
||||||
|
}, nil).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
wantResp: &tweeting.FollowResp{
|
||||||
|
Uid: []string{"user1", "user2"},
|
||||||
|
Page: &tweeting.Pager{
|
||||||
|
Total: 2,
|
||||||
|
Index: 1,
|
||||||
|
Size: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "驗證失敗",
|
||||||
|
input: &tweeting.FollowReq{
|
||||||
|
Uid: "",
|
||||||
|
PageSize: 10,
|
||||||
|
PageIndex: 1,
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
// 模擬驗證失敗
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
wantResp: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "獲取我跟隨的名單失敗",
|
||||||
|
input: &tweeting.FollowReq{
|
||||||
|
Uid: "12345",
|
||||||
|
PageSize: 10,
|
||||||
|
PageIndex: 1,
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
// 模擬驗證通過
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||||
|
// 模擬 GetFollowee 返回錯誤
|
||||||
|
mockSocialNetworkRepository.EXPECT().GetFollowee(gomock.Any(), repository.FollowReq{
|
||||||
|
UID: "12345",
|
||||||
|
PageSize: 10,
|
||||||
|
PageIndex: 1,
|
||||||
|
}).Return(repository.FollowResp{}, errors.New("repository error")).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
wantResp: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 執行測試
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// 設置測試環境
|
||||||
|
tt.prepare()
|
||||||
|
|
||||||
|
// 初始化 GetFolloweeLogic
|
||||||
|
logic := GetFolloweeLogic{
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
ctx: context.TODO(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 執行 GetFollowee
|
||||||
|
got, err := logic.GetFollowee(tt.input)
|
||||||
|
|
||||||
|
// 驗證結果
|
||||||
|
if tt.expectErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, got)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.wantResp, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package socialnetworkservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||||
|
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetFollowerCountLogic_GetFollowerCount(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
// 初始化 mock 依賴
|
||||||
|
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
||||||
|
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||||
|
|
||||||
|
// 初始化服務上下文
|
||||||
|
svcCtx := &svc.ServiceContext{
|
||||||
|
SocialNetworkRepository: mockSocialNetworkRepository,
|
||||||
|
Validate: mockValidate,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試數據集
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input *tweeting.FollowCountReq
|
||||||
|
prepare func()
|
||||||
|
expectErr bool
|
||||||
|
wantResp *tweeting.FollowCountResp
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功獲取跟隨者數量",
|
||||||
|
input: &tweeting.FollowCountReq{
|
||||||
|
Uid: "12345",
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
// 模擬驗證通過
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||||
|
// 模擬 GetFollowerCount 返回正確的結果
|
||||||
|
mockSocialNetworkRepository.EXPECT().GetFollowerCount(gomock.Any(), "12345").Return(int64(10), nil).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
wantResp: &tweeting.FollowCountResp{
|
||||||
|
Uid: "12345",
|
||||||
|
Total: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "驗證失敗",
|
||||||
|
input: &tweeting.FollowCountReq{
|
||||||
|
Uid: "",
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
// 模擬驗證失敗
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
wantResp: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "獲取跟隨者數量失敗",
|
||||||
|
input: &tweeting.FollowCountReq{
|
||||||
|
Uid: "12345",
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
// 模擬驗證通過
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||||
|
// 模擬 GetFollowerCount 返回錯誤
|
||||||
|
mockSocialNetworkRepository.EXPECT().GetFollowerCount(gomock.Any(), "12345").Return(int64(0), errors.New("repository error")).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
wantResp: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 執行測試
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// 設置測試環境
|
||||||
|
tt.prepare()
|
||||||
|
|
||||||
|
// 初始化 GetFollowerCountLogic
|
||||||
|
logic := GetFollowerCountLogic{
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
ctx: context.TODO(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 執行 GetFollowerCount
|
||||||
|
got, err := logic.GetFollowerCount(tt.input)
|
||||||
|
|
||||||
|
// 驗證結果
|
||||||
|
if tt.expectErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, got)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.wantResp, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package socialnetworkservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
"app-cloudep-tweeting-service/internal/domain/repository"
|
||||||
|
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||||
|
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetFollowerLogic_GetFollower(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
// 初始化 mock 依賴
|
||||||
|
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
||||||
|
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||||
|
|
||||||
|
// 初始化服務上下文
|
||||||
|
svcCtx := &svc.ServiceContext{
|
||||||
|
SocialNetworkRepository: mockSocialNetworkRepository,
|
||||||
|
Validate: mockValidate,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試數據集
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input *tweeting.FollowReq
|
||||||
|
prepare func()
|
||||||
|
expectErr bool
|
||||||
|
wantResp *tweeting.FollowResp
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功獲取跟隨者名單",
|
||||||
|
input: &tweeting.FollowReq{
|
||||||
|
Uid: "12345",
|
||||||
|
PageSize: 10,
|
||||||
|
PageIndex: 1,
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
// 模擬驗證通過
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||||
|
// 模擬 GetFollower 返回正確的結果
|
||||||
|
mockSocialNetworkRepository.EXPECT().GetFollower(gomock.Any(), repository.FollowReq{
|
||||||
|
UID: "12345",
|
||||||
|
PageSize: 10,
|
||||||
|
PageIndex: 1,
|
||||||
|
}).Return(repository.FollowResp{
|
||||||
|
UIDs: []string{"user1", "user2"},
|
||||||
|
Total: 2,
|
||||||
|
}, nil).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
wantResp: &tweeting.FollowResp{
|
||||||
|
Uid: []string{"user1", "user2"},
|
||||||
|
Page: &tweeting.Pager{
|
||||||
|
Total: 2,
|
||||||
|
Index: 1,
|
||||||
|
Size: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "驗證失敗",
|
||||||
|
input: &tweeting.FollowReq{
|
||||||
|
Uid: "",
|
||||||
|
PageSize: 10,
|
||||||
|
PageIndex: 1,
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
// 模擬驗證失敗
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
wantResp: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "獲取跟隨者名單失敗",
|
||||||
|
input: &tweeting.FollowReq{
|
||||||
|
Uid: "12345",
|
||||||
|
PageSize: 10,
|
||||||
|
PageIndex: 1,
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
// 模擬驗證通過
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||||
|
// 模擬 GetFollower 返回錯誤
|
||||||
|
mockSocialNetworkRepository.EXPECT().GetFollower(gomock.Any(), repository.FollowReq{
|
||||||
|
UID: "12345",
|
||||||
|
PageSize: 10,
|
||||||
|
PageIndex: 1,
|
||||||
|
}).Return(repository.FollowResp{}, errors.New("repository error")).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
wantResp: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 執行測試
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// 設置測試環境
|
||||||
|
tt.prepare()
|
||||||
|
|
||||||
|
// 初始化 GetFollowerLogic
|
||||||
|
logic := GetFollowerLogic{
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
ctx: context.TODO(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 執行 GetFollower
|
||||||
|
got, err := logic.GetFollower(tt.input)
|
||||||
|
|
||||||
|
// 驗證結果
|
||||||
|
if tt.expectErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, got)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.wantResp, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package socialnetworkservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||||
|
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarkFollowRelationLogic_MarkFollowRelation(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
// 初始化 mock 依賴
|
||||||
|
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
||||||
|
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||||
|
|
||||||
|
// 初始化服務上下文
|
||||||
|
svcCtx := &svc.ServiceContext{
|
||||||
|
SocialNetworkRepository: mockSocialNetworkRepository,
|
||||||
|
Validate: mockValidate,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試數據集
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input *tweeting.DoFollowerRelationReq
|
||||||
|
prepare func()
|
||||||
|
expectErr bool
|
||||||
|
wantResp *tweeting.OKResp
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功建立關注關係",
|
||||||
|
input: &tweeting.DoFollowerRelationReq{
|
||||||
|
FollowerUid: "follower123",
|
||||||
|
FolloweeUid: "followee456",
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
// 模擬驗證通過
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||||
|
// 模擬成功建立關注關係
|
||||||
|
mockSocialNetworkRepository.EXPECT().MarkFollowerRelation(gomock.Any(), "follower123", "followee456").Return(nil).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
wantResp: &tweeting.OKResp{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "驗證失敗",
|
||||||
|
input: &tweeting.DoFollowerRelationReq{
|
||||||
|
FollowerUid: "",
|
||||||
|
FolloweeUid: "",
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
// 模擬驗證失敗
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
wantResp: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "建立關注關係失敗",
|
||||||
|
input: &tweeting.DoFollowerRelationReq{
|
||||||
|
FollowerUid: "follower123",
|
||||||
|
FolloweeUid: "followee456",
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
// 模擬驗證通過
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||||
|
// 模擬建立關注關係失敗
|
||||||
|
mockSocialNetworkRepository.EXPECT().MarkFollowerRelation(gomock.Any(), "follower123", "followee456").Return(errors.New("repository error")).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
wantResp: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 執行測試
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// 設置測試環境
|
||||||
|
tt.prepare()
|
||||||
|
|
||||||
|
// 初始化 MarkFollowRelationLogic
|
||||||
|
logic := MarkFollowRelationLogic{
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
ctx: context.TODO(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 執行 MarkFollowRelation
|
||||||
|
got, err := logic.MarkFollowRelation(tt.input)
|
||||||
|
|
||||||
|
// 驗證結果
|
||||||
|
if tt.expectErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, got)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.wantResp, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package socialnetworkservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||||
|
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRemoveFollowRelationLogic_RemoveFollowRelation(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
// 初始化 mock 依賴
|
||||||
|
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
||||||
|
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||||
|
|
||||||
|
// 初始化服務上下文
|
||||||
|
svcCtx := &svc.ServiceContext{
|
||||||
|
SocialNetworkRepository: mockSocialNetworkRepository,
|
||||||
|
Validate: mockValidate,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試數據集
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input *tweeting.DoFollowerRelationReq
|
||||||
|
prepare func()
|
||||||
|
expectErr bool
|
||||||
|
wantResp *tweeting.OKResp
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功取消關注",
|
||||||
|
input: &tweeting.DoFollowerRelationReq{
|
||||||
|
FollowerUid: "follower123",
|
||||||
|
FolloweeUid: "followee456",
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
// 模擬驗證通過
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||||
|
// 模擬成功刪除關注關係
|
||||||
|
mockSocialNetworkRepository.EXPECT().RemoveFollowerRelation(gomock.Any(), "follower123", "followee456").Return(nil).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
wantResp: &tweeting.OKResp{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "驗證失敗",
|
||||||
|
input: &tweeting.DoFollowerRelationReq{
|
||||||
|
FollowerUid: "",
|
||||||
|
FolloweeUid: "",
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
// 模擬驗證失敗
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
wantResp: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "取消關注失敗",
|
||||||
|
input: &tweeting.DoFollowerRelationReq{
|
||||||
|
FollowerUid: "follower123",
|
||||||
|
FolloweeUid: "followee456",
|
||||||
|
},
|
||||||
|
prepare: func() {
|
||||||
|
// 模擬驗證通過
|
||||||
|
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||||
|
// 模擬刪除關注關係失敗
|
||||||
|
mockSocialNetworkRepository.EXPECT().RemoveFollowerRelation(gomock.Any(), "follower123", "followee456").Return(errors.New("repository error")).Times(1)
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
wantResp: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 執行測試
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// 設置測試環境
|
||||||
|
tt.prepare()
|
||||||
|
|
||||||
|
// 初始化 RemoveFollowRelationLogic
|
||||||
|
logic := RemoveFollowRelationLogic{
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
ctx: context.TODO(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 執行 RemoveFollowRelation
|
||||||
|
got, err := logic.RemoveFollowRelation(tt.input)
|
||||||
|
|
||||||
|
// 驗證結果
|
||||||
|
if tt.expectErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, got)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.wantResp, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: ./internal/domain/repository/social_network.go
|
||||||
|
//
|
||||||
|
// Generated by this command:
|
||||||
|
//
|
||||||
|
// mockgen -source=./internal/domain/repository/social_network.go -destination=./internal/mock/repository/social_network.go -package=mock
|
||||||
|
//
|
||||||
|
|
||||||
|
// Package mock is a generated GoMock package.
|
||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
repository "app-cloudep-tweeting-service/internal/domain/repository"
|
||||||
|
context "context"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockSocialNetworkRepository is a mock of SocialNetworkRepository interface.
|
||||||
|
type MockSocialNetworkRepository struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockSocialNetworkRepositoryMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockSocialNetworkRepositoryMockRecorder is the mock recorder for MockSocialNetworkRepository.
|
||||||
|
type MockSocialNetworkRepositoryMockRecorder struct {
|
||||||
|
mock *MockSocialNetworkRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockSocialNetworkRepository creates a new mock instance.
|
||||||
|
func NewMockSocialNetworkRepository(ctrl *gomock.Controller) *MockSocialNetworkRepository {
|
||||||
|
mock := &MockSocialNetworkRepository{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockSocialNetworkRepositoryMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockSocialNetworkRepository) EXPECT() *MockSocialNetworkRepositoryMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUserNode mocks base method.
|
||||||
|
func (m *MockSocialNetworkRepository) CreateUserNode(ctx context.Context, uid string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateUserNode", ctx, uid)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUserNode indicates an expected call of CreateUserNode.
|
||||||
|
func (mr *MockSocialNetworkRepositoryMockRecorder) CreateUserNode(ctx, uid any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUserNode", reflect.TypeOf((*MockSocialNetworkRepository)(nil).CreateUserNode), ctx, uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDegreeBetweenUsers mocks base method.
|
||||||
|
func (m *MockSocialNetworkRepository) GetDegreeBetweenUsers(ctx context.Context, uid1, uid2 string) (int64, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetDegreeBetweenUsers", ctx, uid1, uid2)
|
||||||
|
ret0, _ := ret[0].(int64)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDegreeBetweenUsers indicates an expected call of GetDegreeBetweenUsers.
|
||||||
|
func (mr *MockSocialNetworkRepositoryMockRecorder) GetDegreeBetweenUsers(ctx, uid1, uid2 any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDegreeBetweenUsers", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetDegreeBetweenUsers), ctx, uid1, uid2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFollowee mocks base method.
|
||||||
|
func (m *MockSocialNetworkRepository) GetFollowee(ctx context.Context, req repository.FollowReq) (repository.FollowResp, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetFollowee", ctx, req)
|
||||||
|
ret0, _ := ret[0].(repository.FollowResp)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFollowee indicates an expected call of GetFollowee.
|
||||||
|
func (mr *MockSocialNetworkRepositoryMockRecorder) GetFollowee(ctx, req any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFollowee", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetFollowee), ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFolloweeCount mocks base method.
|
||||||
|
func (m *MockSocialNetworkRepository) GetFolloweeCount(ctx context.Context, uid string) (int64, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetFolloweeCount", ctx, uid)
|
||||||
|
ret0, _ := ret[0].(int64)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFolloweeCount indicates an expected call of GetFolloweeCount.
|
||||||
|
func (mr *MockSocialNetworkRepositoryMockRecorder) GetFolloweeCount(ctx, uid any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFolloweeCount", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetFolloweeCount), ctx, uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFollower mocks base method.
|
||||||
|
func (m *MockSocialNetworkRepository) GetFollower(ctx context.Context, req repository.FollowReq) (repository.FollowResp, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetFollower", ctx, req)
|
||||||
|
ret0, _ := ret[0].(repository.FollowResp)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFollower indicates an expected call of GetFollower.
|
||||||
|
func (mr *MockSocialNetworkRepositoryMockRecorder) GetFollower(ctx, req any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFollower", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetFollower), ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFollowerCount mocks base method.
|
||||||
|
func (m *MockSocialNetworkRepository) GetFollowerCount(ctx context.Context, uid string) (int64, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetFollowerCount", ctx, uid)
|
||||||
|
ret0, _ := ret[0].(int64)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFollowerCount indicates an expected call of GetFollowerCount.
|
||||||
|
func (mr *MockSocialNetworkRepositoryMockRecorder) GetFollowerCount(ctx, uid any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFollowerCount", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetFollowerCount), ctx, uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUIDsWithinNDegrees mocks base method.
|
||||||
|
func (m *MockSocialNetworkRepository) GetUIDsWithinNDegrees(ctx context.Context, uid string, degrees, pageSize, pageIndex int64) ([]string, int64, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetUIDsWithinNDegrees", ctx, uid, degrees, pageSize, pageIndex)
|
||||||
|
ret0, _ := ret[0].([]string)
|
||||||
|
ret1, _ := ret[1].(int64)
|
||||||
|
ret2, _ := ret[2].(error)
|
||||||
|
return ret0, ret1, ret2
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUIDsWithinNDegrees indicates an expected call of GetUIDsWithinNDegrees.
|
||||||
|
func (mr *MockSocialNetworkRepositoryMockRecorder) GetUIDsWithinNDegrees(ctx, uid, degrees, pageSize, pageIndex any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUIDsWithinNDegrees", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetUIDsWithinNDegrees), ctx, uid, degrees, pageSize, pageIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkFollowerRelation mocks base method.
|
||||||
|
func (m *MockSocialNetworkRepository) MarkFollowerRelation(ctx context.Context, fromUID, toUID string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "MarkFollowerRelation", ctx, fromUID, toUID)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkFollowerRelation indicates an expected call of MarkFollowerRelation.
|
||||||
|
func (mr *MockSocialNetworkRepositoryMockRecorder) MarkFollowerRelation(ctx, fromUID, toUID any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkFollowerRelation", reflect.TypeOf((*MockSocialNetworkRepository)(nil).MarkFollowerRelation), ctx, fromUID, toUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveFollowerRelation mocks base method.
|
||||||
|
func (m *MockSocialNetworkRepository) RemoveFollowerRelation(ctx context.Context, fromUID, toUID string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "RemoveFollowerRelation", ctx, fromUID, toUID)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveFollowerRelation indicates an expected call of RemoveFollowerRelation.
|
||||||
|
func (mr *MockSocialNetworkRepositoryMockRecorder) RemoveFollowerRelation(ctx, fromUID, toUID any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveFollowerRelation", reflect.TypeOf((*MockSocialNetworkRepository)(nil).RemoveFollowerRelation), ctx, fromUID, toUID)
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: ./internal/domain/repository/timeline.go
|
||||||
|
//
|
||||||
|
// Generated by this command:
|
||||||
|
//
|
||||||
|
// mockgen -source=./internal/domain/repository/timeline.go -destination=./internal/mock/repository/timeline.go -package=mock
|
||||||
|
//
|
||||||
|
|
||||||
|
// Package mock is a generated GoMock package.
|
||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
repository "app-cloudep-tweeting-service/internal/domain/repository"
|
||||||
|
context "context"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockTimelineRepository is a mock of TimelineRepository interface.
|
||||||
|
type MockTimelineRepository struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockTimelineRepositoryMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockTimelineRepositoryMockRecorder is the mock recorder for MockTimelineRepository.
|
||||||
|
type MockTimelineRepositoryMockRecorder struct {
|
||||||
|
mock *MockTimelineRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockTimelineRepository creates a new mock instance.
|
||||||
|
func NewMockTimelineRepository(ctrl *gomock.Controller) *MockTimelineRepository {
|
||||||
|
mock := &MockTimelineRepository{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockTimelineRepositoryMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockTimelineRepository) EXPECT() *MockTimelineRepositoryMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPost mocks base method.
|
||||||
|
func (m *MockTimelineRepository) AddPost(ctx context.Context, req repository.AddPostRequest) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "AddPost", ctx, req)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPost indicates an expected call of AddPost.
|
||||||
|
func (mr *MockTimelineRepositoryMockRecorder) AddPost(ctx, req any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPost", reflect.TypeOf((*MockTimelineRepository)(nil).AddPost), ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearNoMoreDataFlag mocks base method.
|
||||||
|
func (m *MockTimelineRepository) ClearNoMoreDataFlag(ctx context.Context, uid string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ClearNoMoreDataFlag", ctx, uid)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearNoMoreDataFlag indicates an expected call of ClearNoMoreDataFlag.
|
||||||
|
func (mr *MockTimelineRepositoryMockRecorder) ClearNoMoreDataFlag(ctx, uid any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearNoMoreDataFlag", reflect.TypeOf((*MockTimelineRepository)(nil).ClearNoMoreDataFlag), ctx, uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchTimeline mocks base method.
|
||||||
|
func (m *MockTimelineRepository) FetchTimeline(ctx context.Context, req repository.FetchTimelineRequest) (repository.FetchTimelineResponse, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "FetchTimeline", ctx, req)
|
||||||
|
ret0, _ := ret[0].(repository.FetchTimelineResponse)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchTimeline indicates an expected call of FetchTimeline.
|
||||||
|
func (mr *MockTimelineRepositoryMockRecorder) FetchTimeline(ctx, req any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchTimeline", reflect.TypeOf((*MockTimelineRepository)(nil).FetchTimeline), ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasNoMoreData mocks base method.
|
||||||
|
func (m *MockTimelineRepository) HasNoMoreData(ctx context.Context, uid string) (bool, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "HasNoMoreData", ctx, uid)
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasNoMoreData indicates an expected call of HasNoMoreData.
|
||||||
|
func (mr *MockTimelineRepositoryMockRecorder) HasNoMoreData(ctx, uid any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasNoMoreData", reflect.TypeOf((*MockTimelineRepository)(nil).HasNoMoreData), ctx, uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNoMoreDataFlag mocks base method.
|
||||||
|
func (m *MockTimelineRepository) SetNoMoreDataFlag(ctx context.Context, uid string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetNoMoreDataFlag", ctx, uid)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNoMoreDataFlag indicates an expected call of SetNoMoreDataFlag.
|
||||||
|
func (mr *MockTimelineRepositoryMockRecorder) SetNoMoreDataFlag(ctx, uid any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNoMoreDataFlag", reflect.TypeOf((*MockTimelineRepository)(nil).SetNoMoreDataFlag), ctx, uid)
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package repository
|
Loading…
Reference in New Issue