test: 为 taskDao 与 taskLogic 添加单元测试

新增 internal/testutil/db.go,基于 DATA-DOG/go-sqlmock 提供
SetupTaskBenchDB 帮助函数,把 mock 出来的 *gorm.DB 挂到
app.ModuleClients.TaskBenchDB 上,方便 dao / logic 层单测。
SQL 匹配使用 QueryMatcherRegexp,避免对 gorm 生成的 SQL 字面量
(字段顺序、占位符数量等)过度耦合。

- internal/dao/taskDao_test.go:覆盖 GetPendingTaskLayout 等
  DAO 方法的 SQL 行为与错误分支。
- internal/logic/taskLogic_test.go:覆盖 CreateTaskWorkLog
  的入参范围校验分支。

go.mod / go.sum:
- 新增直接依赖 github.com/DATA-DOG/go-sqlmock v1.5.2、
  github.com/stretchr/testify v1.11.1;
- 调整 google/uuid、robfig/cron/v3、satori/go.uuid 为直接依赖。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cjy 2026-06-10 15:20:21 +08:00
parent 038f07f5af
commit 590f0dedd6
5 changed files with 407 additions and 6 deletions

11
go.mod
View File

@ -4,6 +4,7 @@ go 1.23.3
require (
dubbo.apache.org/dubbo-go/v3 v3.0.2
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704
github.com/bwmarrin/snowflake v0.3.0
github.com/dubbogo/grpc-go v1.42.9
@ -13,18 +14,21 @@ require (
github.com/gin-gonic/gin v1.9.0
github.com/go-redis/redis v6.15.9+incompatible
github.com/golang/protobuf v1.5.2
github.com/google/uuid v1.6.0
github.com/google/wire v0.6.0
github.com/jinzhu/copier v0.3.5
github.com/mwitkow/go-proto-validators v0.3.2
github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/opentracing/opentracing-go v1.2.0
github.com/robfig/cron/v3 v3.0.1
github.com/samber/lo v1.51.0
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b
github.com/shopspring/decimal v1.4.0
github.com/spf13/viper v1.7.1
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271
github.com/stretchr/testify v1.11.1
github.com/uber/jaeger-client-go v2.30.0+incompatible
go.uber.org/zap v1.24.0
google.golang.org/grpc v1.54.0
google.golang.org/protobuf v1.29.1
gorm.io/datatypes v1.2.5
gorm.io/driver/mysql v1.5.6
@ -81,7 +85,6 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
@ -114,6 +117,7 @@ require (
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/polarismesh/polaris-go v1.1.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/prometheus/client_golang v1.12.2 // indirect
@ -121,8 +125,6 @@ require (
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/prometheus/statsd_exporter v0.21.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect
github.com/shirou/gopsutil/v3 v3.22.2 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/afero v1.9.2 // indirect
@ -154,6 +156,7 @@ require (
golang.org/x/text v0.22.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.54.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

27
go.sum
View File

@ -49,7 +49,10 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
@ -251,6 +254,7 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
@ -294,6 +298,7 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
@ -329,7 +334,9 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
@ -396,6 +403,7 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -522,9 +530,13 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
@ -572,6 +584,7 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/knadh/koanf v1.4.1/go.mod h1:1cfH5223ZeZUOs8FU2UdTmaNfHpqgtjV0+NHjRO43gs=
@ -625,10 +638,12 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
@ -719,6 +734,7 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -813,6 +829,7 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
@ -852,8 +869,9 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -863,7 +881,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ=
@ -1252,6 +1271,7 @@ golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -1503,9 +1523,12 @@ gorm.io/datatypes v1.2.5/go.mod h1:I5FUdlKpLb5PMqeMQhm30CQ6jXP8Rj89xkTeCSAaAD4=
gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8=
gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U=
gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A=
gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c=
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g=
gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g=
gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.23.0/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=

View File

@ -0,0 +1,95 @@
package dao
import (
"errors"
"regexp"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
"micro-bundle/internal/testutil"
)
// ---- GetPendingTaskLayout ----
// sqlmock + gorm 配合:QueryMatcherEqual 要求 SQL 字面量精确匹配;但
// gorm 生成的字段顺序与时间字段处理可能因版本略有差异,这里用 Regexp 匹配
// 关键子串,避免对生成 SQL 的细节过度耦合。
//
// DAO 测试不用 t.Parallel():SetupTaskBenchDB 会改写全局 app.ModuleClients,
// 并行执行会导致 sqlmock 期望链互相污染。
var reSelectLayout = regexp.MustCompile(`SELECT \* FROM .task_pending_layout. WHERE id = \?`)
var reInsertLayout = regexp.MustCompile(`INSERT INTO .task_pending_layout. .*\)\s*VALUES \(.*\)\s*ON DUPLICATE KEY UPDATE .data.`)
var reInsertHidden = regexp.MustCompile(`INSERT INTO .task_assignee_hidden.`)
func TestGetPendingTaskLayout_NotFound(t *testing.T) {
mock := testutil.SetupTaskBenchDB(t)
mock.MatchExpectationsInOrder(false)
mock.ExpectQuery(reSelectLayout.String()).
WithArgs(1).
WillReturnError(gormNotFound())
got, err := GetPendingTaskLayout()
require.Error(t, err)
assert.Empty(t, got)
}
func TestGetPendingTaskLayout_Success(t *testing.T) {
mock := testutil.SetupTaskBenchDB(t)
mock.MatchExpectationsInOrder(false)
rows := sqlmock.NewRows([]string{"id", "data"}).AddRow(1, `[{"fieldKey":"subNum"}]`)
// gorm 的 First() 内部会带一个 LIMIT 参数,这里用 .* 兼容。
mock.ExpectQuery(reSelectLayout.String()).
WillReturnRows(rows)
got, err := GetPendingTaskLayout()
require.NoError(t, err)
assert.JSONEq(t, `[{"fieldKey":"subNum"}]`, got)
}
// ---- SetPendingTaskLayout ----
func TestSetPendingTaskLayout_Success(t *testing.T) {
mock := testutil.SetupTaskBenchDB(t)
mock.MatchExpectationsInOrder(false)
// gorm 会在 Create + OnConflict 时开事务,显式声明 Begin/Commit。
mock.ExpectBegin()
mock.ExpectExec(reInsertLayout.String()).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
err := SetPendingTaskLayout(`[{"fieldKey":"subNum"}]`)
require.NoError(t, err)
}
// ---- AddHiddenTaskAssignee ----
func TestAddHiddenTaskAssignee_Success(t *testing.T) {
mock := testutil.SetupTaskBenchDB(t)
mock.MatchExpectationsInOrder(false)
mock.ExpectBegin()
mock.ExpectExec(reInsertHidden.String()).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
err := AddHiddenTaskAssignee("员工A", "E001")
require.NoError(t, err)
}
// ---- helpers ----
// gormNotFound 返回 gorm.ErrRecordNotFound。
// gorm 内部 First()/Find() 命中零行时返回该错误。
func gormNotFound() error {
return gorm.ErrRecordNotFound
}
// 保留 errors 包,留给后续事务测试使用 dialError 等
var _ = errors.New

View File

@ -0,0 +1,233 @@
package logic
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"micro-bundle/internal/dto"
)
// ---- CreateTaskWorkLog 范围校验 ----
func TestCreateTaskWorkLog_Validation(t *testing.T) {
t.Parallel()
base := func() *dto.CreateTaskWorkLogRequest {
return &dto.CreateTaskWorkLogRequest{
AssignRecordsUUID: "uuid-1",
WorkUUID: "work-1",
OperationType: 1, // 1-加
TaskType: 1, // 1-视频
TaskCount: 1,
}
}
tests := []struct {
name string
mutate func(r *dto.CreateTaskWorkLogRequest)
wantContain string
}{
// ReturnError(nil, "参数错误", ...) 实际返回的 msg 统一是 "参数错误",
// 详细说明只在 print 字段(不入错误链),所以断言统一为 "参数错误",
// 区分点放在测试名上。
{"OperationType为0", func(r *dto.CreateTaskWorkLogRequest) { r.OperationType = 0 }, "参数错误"},
{"OperationType为5", func(r *dto.CreateTaskWorkLogRequest) { r.OperationType = 5 }, "参数错误"},
{"TaskType为0", func(r *dto.CreateTaskWorkLogRequest) { r.TaskType = 0 }, "参数错误"},
{"TaskType为7", func(r *dto.CreateTaskWorkLogRequest) { r.TaskType = 7 }, "参数错误"},
{"TaskCount为负", func(r *dto.CreateTaskWorkLogRequest) { r.TaskCount = -1 }, "参数错误"},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
r := base()
tc.mutate(r)
err := CreateTaskWorkLog(r)
require.Error(t, err)
assert.Contains(t, err.Error(), tc.wantContain)
})
}
}
// ---- BatchAssignTask 校验 ----
func TestBatchAssignTask_EmptyItems(t *testing.T) {
t.Parallel()
// nil 和 空 slice 都走 `ReturnError(nil, "参数错误", "批量指派项不能为空")`,
// 实际返回的 msg 是 "参数错误"。
// nil slice
err := BatchAssignTask(nil)
require.Error(t, err)
assert.Contains(t, err.Error(), "参数错误")
// 空 slice
err = BatchAssignTask([]*dto.BatchAssignItem{})
require.Error(t, err)
assert.Contains(t, err.Error(), "参数错误")
}
func TestBatchAssignTask_NilItem(t *testing.T) {
t.Parallel()
// 同样 `ReturnError(nil, "参数错误", "存在空的指派项")`。
err := BatchAssignTask([]*dto.BatchAssignItem{nil})
require.Error(t, err)
assert.Contains(t, err.Error(), "参数错误")
}
func TestBatchAssignTask_NegativeCount(t *testing.T) {
t.Parallel()
make := func(mutate func(*dto.BatchAssignItem)) *dto.BatchAssignItem {
it := &dto.BatchAssignItem{
SubNum: "S001",
TaskAssignee: "员工A",
TaskAssigneeNum: "E001",
AssignVideoCount: 1,
}
mutate(it)
return it
}
tests := []struct {
name string
mutate func(*dto.BatchAssignItem)
}{
{"Video为负", func(it *dto.BatchAssignItem) { it.AssignVideoCount = -1 }},
{"Post为负", func(it *dto.BatchAssignItem) { it.AssignPostCount = -1 }},
{"Data为负", func(it *dto.BatchAssignItem) { it.AssignDataCount = -1 }},
{"VideoScript为负", func(it *dto.BatchAssignItem) { it.AssignVideoScriptCount = -1 }},
{"Report为负", func(it *dto.BatchAssignItem) { it.AssignReportCount = -1 }},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
err := BatchAssignTask([]*dto.BatchAssignItem{make(tc.mutate)})
require.Error(t, err)
assert.Contains(t, err.Error(), "指派数量不能小于0")
})
}
}
func TestBatchAssignTask_SingleItemAllZero(t *testing.T) {
t.Parallel()
it := &dto.BatchAssignItem{SubNum: "S001", TaskAssignee: "员工A", TaskAssigneeNum: "E001"}
err := BatchAssignTask([]*dto.BatchAssignItem{it})
require.Error(t, err)
assert.Contains(t, err.Error(), "指派数量不能全部为0")
}
// 注:logic 中"全部指派项数量都为 0"的外层兜底(`if !hasPositive`)实际不可达:
// 循环中每项的"单项全 0"检查会先返回,所以该分支永远走不到。
// 保留这条注释供后人参考,不写不可达测试。
// ---- TerminateTaskByUUID / BatchTerminateTaskByUUIDs ----
func TestTerminateTaskByUUID_Empty(t *testing.T) {
t.Parallel()
err := TerminateTaskByUUID("")
require.Error(t, err)
assert.Contains(t, err.Error(), "参数错误")
}
func TestBatchTerminateTaskByUUIDs_Empty(t *testing.T) {
t.Parallel()
success, failed, failedUUIDs, err := BatchTerminateTaskByUUIDs(nil)
require.Error(t, err)
assert.Equal(t, 0, success)
assert.Equal(t, 0, failed)
assert.Nil(t, failedUUIDs)
assert.Contains(t, err.Error(), "参数错误")
success, failed, failedUUIDs, err = BatchTerminateTaskByUUIDs([]string{})
require.Error(t, err)
assert.Equal(t, 0, success)
assert.Equal(t, 0, failed)
assert.Nil(t, failedUUIDs)
}
// ---- RevertTaskCompletionByUUIDItem ----
func TestRevertTaskCompletionByUUIDItem_Empty(t *testing.T) {
t.Parallel()
for _, in := range []string{"", " ", "\t\n"} {
err := RevertTaskCompletionByUUIDItem(in)
require.Error(t, err, "input=%q", in)
assert.Contains(t, err.Error(), "参数错误")
}
}
// ---- GetTaskActualStatusByUUID ----
func TestGetTaskActualStatusByUUID_Empty(t *testing.T) {
t.Parallel()
for _, in := range []string{"", " "} {
status, err := GetTaskActualStatusByUUID(in)
require.Error(t, err, "input=%q", in)
assert.Equal(t, 0, status)
assert.Contains(t, err.Error(), "查询参数错误")
}
}
// ---- AddHiddenTaskAssignee ----
func TestAddHiddenTaskAssignee_Empty(t *testing.T) {
t.Parallel()
tests := []struct {
name, assignee, num string
}{
{"都为空", "", ""},
{"名称空", "", "E001"},
{"账号空", "员工A", ""},
{"全空格", " ", " "},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
err := AddHiddenTaskAssignee(tc.assignee, tc.num)
require.Error(t, err)
assert.Contains(t, err.Error(), "参数错误")
})
}
}
// ---- buildDefaultPendingLayout (私有函数,同包内可直接测) ----
func TestBuildDefaultPendingLayout(t *testing.T) {
t.Parallel()
got := buildDefaultPendingLayout()
require.NotEmpty(t, got)
var rows []map[string]any
err := json.Unmarshal([]byte(got), &rows)
require.NoError(t, err, "应当是合法 JSON")
// 33 个字段是当前硬编码的默认值;如未来调整,只改这一个数字。
assert.Len(t, rows, 33)
// 每个字段必有 fieldKey / fieldValue / sortOrNot / status
for i, row := range rows {
assert.Contains(t, row, "fieldKey", "第 %d 行缺 fieldKey", i)
assert.Contains(t, row, "fieldValue", "第 %d 行缺 fieldValue", i)
assert.Contains(t, row, "sortOrNot", "第 %d 行缺 sortOrNot", i)
assert.Contains(t, row, "status", "第 %d 行缺 status", i)
}
}
// GetPendingTaskLayout 的 dao 降级路径(失败时回退默认布局)留作下一阶段,
// 那里需要 sqlmock 配合 dao 验证。纯函数 buildDefaultPendingLayout 已被
// TestBuildDefaultPendingLayout 覆盖。

47
internal/testutil/db.go Normal file
View File

@ -0,0 +1,47 @@
package testutil
import (
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"micro-bundle/pkg/app"
"micro-bundle/pkg/db"
)
// SetupTaskBenchDB 创建一个 sqlmock 驱动的 *gorm.DB,挂到 app.ModuleClients.TaskBenchDB。
// 返回的 sqlmock 用于在测试中声明期望 SQL,cleanup 自动还原全局单例。
//
// 使用 QueryMatcherRegexp 匹配,SQL 文本作为正则匹配(支持 .* 等),
// 避免对 gorm 生成 SQL 的细节(字段顺序、占位符数量)过度耦合。
func SetupTaskBenchDB(t *testing.T) sqlmock.Sqlmock {
t.Helper()
mockDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherRegexp))
require.NoError(t, err)
gormDB, err := gorm.Open(mysql.New(mysql.Config{
Conn: mockDB,
SkipInitializeWithVersion: true,
}), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
DisableForeignKeyConstraintWhenMigrating: true,
})
require.NoError(t, err)
original := app.ModuleClients
app.ModuleClients = &app.App{
Lg: zap.NewNop(),
TaskBenchDB: &db.TaskBenchDB{DB: gormDB},
}
t.Cleanup(func() { app.ModuleClients = original })
return mock
}