feat(annotations): annotations service definition (#21629)
* feat(annotations): annotations service definition * feat: generate mock for annotations service * fix: use utf8.RuneCountInString; nowFunc as a parameterpull/21637/head^2
parent
a187292632
commit
b1ef9f668c
|
@ -0,0 +1,376 @@
|
|||
package influxdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/influxdata/influxdb/v2/kit/platform"
|
||||
"github.com/influxdata/influxdb/v2/kit/platform/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
errEmptySummary = &errors.Error{
|
||||
Code: errors.EInvalid,
|
||||
Msg: "summary cannot be empty",
|
||||
}
|
||||
errSummaryTooLong = &errors.Error{
|
||||
Code: errors.EInvalid,
|
||||
Msg: "summary must be less than 255 characters",
|
||||
}
|
||||
errStreamTagTooLong = &errors.Error{
|
||||
Code: errors.EInvalid,
|
||||
Msg: "stream tag must be less than 255 characters",
|
||||
}
|
||||
errStreamNameTooLong = &errors.Error{
|
||||
Code: errors.EInvalid,
|
||||
Msg: "stream name must be less than 255 characters",
|
||||
}
|
||||
errStreamDescTooLong = &errors.Error{
|
||||
Code: errors.EInvalid,
|
||||
Msg: "stream description must be less than 1024 characters",
|
||||
}
|
||||
errStickerTooLong = &errors.Error{
|
||||
Code: errors.EInvalid,
|
||||
Msg: "stickers must be less than 255 characters",
|
||||
}
|
||||
errMsgTooLong = &errors.Error{
|
||||
Code: errors.EInvalid,
|
||||
Msg: "message must be less than 4096 characters",
|
||||
}
|
||||
errReversedTimes = &errors.Error{
|
||||
Code: errors.EInvalid,
|
||||
Msg: "start time must come before end time",
|
||||
}
|
||||
errMissingStreamName = &errors.Error{
|
||||
Code: errors.EInvalid,
|
||||
Msg: "stream name must be set",
|
||||
}
|
||||
errMissingStreamTagOrId = &errors.Error{
|
||||
Code: errors.EInvalid,
|
||||
Msg: "stream tag or id must be set",
|
||||
}
|
||||
errMissingEndTime = &errors.Error{
|
||||
Code: errors.EInvalid,
|
||||
Msg: "end time must be set",
|
||||
}
|
||||
errMissingStartTime = &errors.Error{
|
||||
Code: errors.EInvalid,
|
||||
Msg: "start time must be set",
|
||||
}
|
||||
)
|
||||
|
||||
// Service is the service contract for Annotations
|
||||
type AnnotationService interface {
|
||||
// CreateAnnotations creates annotations.
|
||||
CreateAnnotations(ctx context.Context, orgID platform.ID, create []AnnotationCreate) ([]AnnotationEvent, error)
|
||||
// ListAnnotations lists all annotations matching the filter.
|
||||
ListAnnotations(ctx context.Context, orgID platform.ID, filter AnnotationListFilter) (ReadAnnotations, error)
|
||||
// GetAnnotation gets an annotation by id.
|
||||
GetAnnotation(ctx context.Context, orgID, id platform.ID) (*AnnotationEvent, error)
|
||||
// DeleteAnnotations deletes annotations matching the filter.
|
||||
DeleteAnnotations(ctx context.Context, orgID platform.ID, delete AnnotationDeleteFilter) error
|
||||
// DeleteAnnotation deletes an annotation by id.
|
||||
DeleteAnnotation(ctx context.Context, orgID, id platform.ID) error
|
||||
// UpdateAnnotation updates an annotation.
|
||||
UpdateAnnotation(ctx context.Context, orgID, id platform.ID, update AnnotationCreate) (*AnnotationEvent, error)
|
||||
|
||||
// ListStreams lists all streams matching the filter.
|
||||
ListStreams(ctx context.Context, orgID platform.ID, filter StreamListFilter) ([]ReadStream, error)
|
||||
// CreateOrUpdateStream creates or updates the matching stream by name.
|
||||
CreateOrUpdateStream(ctx context.Context, orgID platform.ID, stream Stream) (*ReadStream, error)
|
||||
// UpdateStream updates the stream by the ID.
|
||||
UpdateStream(ctx context.Context, orgID, id platform.ID, stream Stream) (*ReadStream, error)
|
||||
// DeleteStream deletes the stream metadata by name.
|
||||
DeleteStream(ctx context.Context, orgID platform.ID, streamName string) error
|
||||
// DeleteStreamByID deletes the stream metadata by id.
|
||||
DeleteStreamByID(ctx context.Context, orgID, id platform.ID) error
|
||||
}
|
||||
|
||||
// AnnotationEvent contains fields for annotating an event.
|
||||
type AnnotationEvent struct {
|
||||
ID platform.ID `json:"id,omitempty"` // ID is the annotation ID.
|
||||
AnnotationCreate // AnnotationCreate defines the common input/output bits of an annotation.
|
||||
}
|
||||
|
||||
// AnnotationCreate contains user providable fields for annotating an event.
|
||||
type AnnotationCreate struct {
|
||||
StreamTag string `json:"stream,omitempty"` // StreamTag provides a means to logically group a set of annotated events.
|
||||
Summary string `json:"summary"` // Summary is the only field required to annotate an event.
|
||||
Message string `json:"message,omitempty"` // Message provides more details about the event being annotated.
|
||||
Stickers map[string]string `json:"stickers,omitempty"` // Stickers are like tags, but named something obscure to differentiate them from influx tags. They are there to differentiate an annotated event.
|
||||
EndTime *time.Time `json:"endTime,omitempty"` // EndTime is the time of the event being annotated. Defaults to now if not set.
|
||||
StartTime *time.Time `json:"startTime,omitempty"` // StartTime is the start time of the event being annotated. Defaults to EndTime if not set.
|
||||
}
|
||||
|
||||
// StoredAnnotation represents annotation data to be stored in the database.
|
||||
type StoredAnnotation struct {
|
||||
ID platform.ID `db:"id"` // ID is the annotation's id.
|
||||
OrgID platform.ID `db:"org_id"` // OrgID is the annotations's owning organization.
|
||||
StreamID platform.ID `db:"stream_id"` // StreamID is the id of a stream.
|
||||
StreamTag string `db:"name"` // StreamTag is the name of a stream (when selecting with join of streams).
|
||||
Summary string `db:"summary"` // Summary is the summary of the annotated event.
|
||||
Message string `db:"message"` // Message is a longer description of the annotated event.
|
||||
Stickers []string `db:"stickers"` // Stickers are additional labels to group annotations by.
|
||||
Duration string `db:"duration"` // Duration is the time range (with zone) of an annotated event.
|
||||
Lower string `db:"lower"` // Lower is the time an annotated event beings.
|
||||
Upper string `db:"upper"` // Upper is the time an annotated event ends.
|
||||
}
|
||||
|
||||
// Validate validates the creation object.
|
||||
func (a *AnnotationCreate) Validate(nowFunc func() time.Time) error {
|
||||
switch s := utf8.RuneCountInString(a.Summary); {
|
||||
case s <= 0:
|
||||
return errEmptySummary
|
||||
case s > 255:
|
||||
return errSummaryTooLong
|
||||
}
|
||||
|
||||
switch t := utf8.RuneCountInString(a.StreamTag); {
|
||||
case t == 0:
|
||||
a.StreamTag = "default"
|
||||
case t > 255:
|
||||
return errStreamTagTooLong
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(a.Message) > 4096 {
|
||||
return errMsgTooLong
|
||||
}
|
||||
|
||||
for k, v := range a.Stickers {
|
||||
if utf8.RuneCountInString(k) > 255 || utf8.RuneCountInString(v) > 255 {
|
||||
return errStickerTooLong
|
||||
}
|
||||
}
|
||||
|
||||
now := nowFunc()
|
||||
if a.EndTime == nil {
|
||||
a.EndTime = &now
|
||||
}
|
||||
|
||||
if a.StartTime == nil {
|
||||
a.StartTime = a.EndTime
|
||||
}
|
||||
|
||||
if a.EndTime.Before(*(a.StartTime)) {
|
||||
return errReversedTimes
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AnnotationDeleteFilter contains fields for deleting an annotated event.
|
||||
type AnnotationDeleteFilter struct {
|
||||
StreamTag string `json:"stream,omitempty"` // StreamTag provides a means to logically group a set of annotated events.
|
||||
StreamID platform.ID `json:"streamID,omitempty"` // StreamID provides a means to logically group a set of annotated events.
|
||||
Stickers map[string]string `json:"stickers,omitempty"` // Stickers are like tags, but named something obscure to differentiate them from influx tags. They are there to differentiate an annotated event.
|
||||
EndTime *time.Time `json:"endTime,omitempty"` // EndTime is the time of the event being annotated. Defaults to now if not set.
|
||||
StartTime *time.Time `json:"startTime,omitempty"` // StartTime is the start time of the event being annotated. Defaults to EndTime if not set.
|
||||
}
|
||||
|
||||
// Validate validates the deletion object.
|
||||
func (a *AnnotationDeleteFilter) Validate() error {
|
||||
var errs []string
|
||||
|
||||
if len(a.StreamTag) == 0 && !a.StreamID.Valid() {
|
||||
errs = append(errs, errMissingStreamTagOrId.Error())
|
||||
}
|
||||
|
||||
if a.EndTime == nil {
|
||||
errs = append(errs, errMissingEndTime.Error())
|
||||
}
|
||||
|
||||
if a.StartTime == nil {
|
||||
errs = append(errs, errMissingStartTime.Error())
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return &errors.Error{
|
||||
Code: errors.EInvalid,
|
||||
Msg: strings.Join(errs, "; "),
|
||||
}
|
||||
}
|
||||
|
||||
if a.EndTime.Before(*(a.StartTime)) {
|
||||
return errReversedTimes
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var dre = regexp.MustCompile(`stickers\[(.*)\]`)
|
||||
|
||||
// SetStickers sets the stickers from the query parameters.
|
||||
func (a *AnnotationDeleteFilter) SetStickers(vals map[string][]string) {
|
||||
if a.Stickers == nil {
|
||||
a.Stickers = map[string]string{}
|
||||
}
|
||||
|
||||
for k, v := range vals {
|
||||
if ss := dre.FindStringSubmatch(k); len(ss) == 2 && len(v) > 0 {
|
||||
a.Stickers[ss[1]] = v[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AnnotationList defines the structure of the response when listing annotations.
|
||||
type AnnotationList struct {
|
||||
StreamTag string `json:"stream"`
|
||||
Annotations []ReadAnnotation `json:"annotations"`
|
||||
}
|
||||
|
||||
// ReadAnnotations allows annotations to be assigned to a stream.
|
||||
type ReadAnnotations map[string][]ReadAnnotation
|
||||
|
||||
// MarshalJSON allows us to marshal the annotations belonging to a stream properly.
|
||||
func (s ReadAnnotations) MarshalJSON() ([]byte, error) {
|
||||
annotationList := []AnnotationList{}
|
||||
|
||||
for k, v := range s {
|
||||
annotationList = append(annotationList, AnnotationList{
|
||||
StreamTag: k,
|
||||
Annotations: v,
|
||||
})
|
||||
}
|
||||
|
||||
return json.Marshal(annotationList)
|
||||
}
|
||||
|
||||
// ReadAnnotation defines the simplest form of an annotation to be returned. Essentially, it's AnnotationEvent without stream info.
|
||||
type ReadAnnotation struct {
|
||||
ID platform.ID `json:"id"` // ID is the annotation's generated id.
|
||||
Summary string `json:"summary"` // Summary is the only field required to annotate an event.
|
||||
Message string `json:"message,omitempty"` // Message provides more details about the event being annotated.
|
||||
Stickers map[string]string `json:"stickers,omitempty"` // Stickers are like tags, but named something obscure to differentiate them from influx tags. They are there to differentiate an annotated event.
|
||||
EndTime string `json:"endTime"` // EndTime is the time of the event being annotated.
|
||||
StartTime string `json:"startTime,omitempty"` // StartTime is the start time of the event being annotated.
|
||||
}
|
||||
|
||||
// AnnotationListFilter is a selection filter for listing annotations.
|
||||
type AnnotationListFilter struct {
|
||||
StickerIncludes map[string]string `json:"stickerIncludes,omitempty"` // StickerIncludes allows the user to filter annotated events based on it's sticker.
|
||||
StreamIncludes []string `json:"streamIncludes,omitempty"` // StreamIncludes allows the user to filter annotated events by stream.
|
||||
BasicFilter
|
||||
}
|
||||
|
||||
// Validate validates the filter.
|
||||
func (f *AnnotationListFilter) Validate(nowFunc func() time.Time) error {
|
||||
return f.BasicFilter.Validate(nowFunc)
|
||||
}
|
||||
|
||||
var re = regexp.MustCompile(`stickerIncludes\[(.*)\]`)
|
||||
|
||||
// SetStickerIncludes sets the stickerIncludes from the query parameters.
|
||||
func (f *AnnotationListFilter) SetStickerIncludes(vals map[string][]string) {
|
||||
if f.StickerIncludes == nil {
|
||||
f.StickerIncludes = map[string]string{}
|
||||
}
|
||||
|
||||
for k, v := range vals {
|
||||
if ss := re.FindStringSubmatch(k); len(ss) == 2 && len(v) > 0 {
|
||||
f.StickerIncludes[ss[1]] = v[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StreamListFilter is a selection filter for listing streams. Streams are not considered first class resources, but depend on an annotation using them.
|
||||
type StreamListFilter struct {
|
||||
StreamIncludes []string `json:"streamIncludes,omitempty"` // StreamIncludes allows the user to filter streams returned.
|
||||
BasicFilter
|
||||
}
|
||||
|
||||
// Validate validates the filter.
|
||||
func (f *StreamListFilter) Validate(nowFunc func() time.Time) error {
|
||||
return f.BasicFilter.Validate(nowFunc)
|
||||
}
|
||||
|
||||
// Stream defines the stream metadata. Used in create and update requests/responses. Delete requests will only require stream name.
|
||||
type Stream struct {
|
||||
Name string `json:"stream"` // Name is the name of a stream.
|
||||
Description string `json:"description,omitempty"` // Description is more information about a stream.
|
||||
}
|
||||
|
||||
// ReadStream defines the returned stream.
|
||||
type ReadStream struct {
|
||||
ID platform.ID `json:"id" db:"id"` // ID is the id of a stream.
|
||||
Name string `json:"stream" db:"name"` // Name is the name of a stream.
|
||||
Description string `json:"description,omitempty" db:"description"` // Description is more information about a stream.
|
||||
CreatedAt time.Time `json:"createdAt" db:"created_at"` // CreatedAt is a timestamp.
|
||||
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"` // UpdatedAt is a timestamp.
|
||||
}
|
||||
|
||||
// IsValid validates the stream.
|
||||
func (s *Stream) Validate(strict bool) error {
|
||||
switch nameChars := utf8.RuneCountInString(s.Name); {
|
||||
case nameChars <= 0:
|
||||
if strict {
|
||||
return errMissingStreamName
|
||||
}
|
||||
s.Name = "default"
|
||||
case nameChars > 255:
|
||||
return errStreamNameTooLong
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(s.Description) > 1024 {
|
||||
return errStreamDescTooLong
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StoredStream represents stream data to be stored in the metadata database.
|
||||
type StoredStream struct {
|
||||
ID platform.ID `db:"id"` // ID is the stream's id.
|
||||
OrgID platform.ID `db:"org_id"` // OrgID is the stream's owning organization.
|
||||
Name string `db:"name"` // Name is the name of a stream.
|
||||
Description string `db:"description"` // Description is more information about a stream.
|
||||
CreatedAt time.Time `db:"created_at"` // CreatedAt is a timestamp.
|
||||
UpdatedAt time.Time `db:"updated_at"` // UpdatedAt is a timestamp.
|
||||
}
|
||||
|
||||
// BasicStream defines a stream by name. Used for stream deletes.
|
||||
type BasicStream struct {
|
||||
Names []string `json:"stream"`
|
||||
}
|
||||
|
||||
// IsValid validates the stream is not empty.
|
||||
func (s BasicStream) IsValid() bool {
|
||||
if len(s.Names) <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range s.Names {
|
||||
if len(s.Names[i]) <= 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// BasicFilter defines common filter options.
|
||||
type BasicFilter struct {
|
||||
StartTime *time.Time `json:"startTime,omitempty"` // StartTime is the time the event being annotated started.
|
||||
EndTime *time.Time `json:"endTime,omitempty"` // EndTime is the time the event being annotated ended.
|
||||
}
|
||||
|
||||
// Validate validates the basic filter options, setting sane defaults where appropriate.
|
||||
func (f *BasicFilter) Validate(nowFunc func() time.Time) error {
|
||||
now := nowFunc().UTC().Truncate(time.Second)
|
||||
if f.EndTime == nil || f.EndTime.IsZero() {
|
||||
f.EndTime = &now
|
||||
}
|
||||
|
||||
if f.StartTime == nil {
|
||||
f.StartTime = &time.Time{}
|
||||
}
|
||||
|
||||
if f.EndTime.Before(*(f.StartTime)) {
|
||||
return errReversedTimes
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,556 @@
|
|||
package influxdb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/influxdb/v2/kit/platform"
|
||||
"github.com/influxdata/influxdb/v2/kit/platform/errors"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
testTime time.Time = time.Now()
|
||||
testTime2 time.Time = testTime.Add(time.Minute)
|
||||
|
||||
annID, _ = platform.IDFromString("2345678901234567")
|
||||
)
|
||||
|
||||
func nowFunc() time.Time {
|
||||
return testTime
|
||||
}
|
||||
|
||||
func TestAnnotationCreate(t *testing.T) {
|
||||
type tst struct {
|
||||
name string
|
||||
input AnnotationCreate
|
||||
expected AnnotationCreate
|
||||
err *errors.Error
|
||||
}
|
||||
|
||||
tests := []tst{
|
||||
{
|
||||
name: "minimum valid create request",
|
||||
input: AnnotationCreate{
|
||||
Summary: "this is a default annotation",
|
||||
},
|
||||
expected: AnnotationCreate{
|
||||
StreamTag: "default",
|
||||
Summary: "this is a default annotation",
|
||||
EndTime: &testTime,
|
||||
StartTime: &testTime,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "full valid create request",
|
||||
input: AnnotationCreate{
|
||||
StreamTag: "other",
|
||||
Summary: "this is another annotation",
|
||||
Message: "This is a much longer description or message to add to the annotation summary",
|
||||
Stickers: map[string]string{"product": "cloud"},
|
||||
EndTime: &testTime2,
|
||||
StartTime: &testTime,
|
||||
},
|
||||
expected: AnnotationCreate{
|
||||
StreamTag: "other",
|
||||
Summary: "this is another annotation",
|
||||
Message: "This is a much longer description or message to add to the annotation summary",
|
||||
Stickers: map[string]string{"product": "cloud"},
|
||||
EndTime: &testTime2,
|
||||
StartTime: &testTime,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty create request",
|
||||
input: AnnotationCreate{},
|
||||
err: errEmptySummary,
|
||||
},
|
||||
{
|
||||
name: "end time before start create request",
|
||||
input: AnnotationCreate{
|
||||
Summary: "this is a default annotation",
|
||||
EndTime: &testTime,
|
||||
StartTime: &testTime2,
|
||||
},
|
||||
err: errReversedTimes,
|
||||
},
|
||||
{
|
||||
name: "default end time before start create request",
|
||||
input: AnnotationCreate{
|
||||
Summary: "this is a default annotation",
|
||||
StartTime: &testTime2,
|
||||
},
|
||||
err: errReversedTimes,
|
||||
},
|
||||
{
|
||||
name: "summary too long",
|
||||
input: AnnotationCreate{
|
||||
Summary: strings.Repeat("a", 256),
|
||||
},
|
||||
err: errSummaryTooLong,
|
||||
},
|
||||
{
|
||||
name: "message too long",
|
||||
input: AnnotationCreate{
|
||||
Summary: "longTom",
|
||||
Message: strings.Repeat("a", 4097),
|
||||
},
|
||||
err: errMsgTooLong,
|
||||
},
|
||||
{
|
||||
name: "stream tag too long",
|
||||
input: AnnotationCreate{
|
||||
Summary: "longTom",
|
||||
StreamTag: strings.Repeat("a", 256),
|
||||
},
|
||||
err: errStreamTagTooLong,
|
||||
},
|
||||
{
|
||||
name: "sticker key too long",
|
||||
input: AnnotationCreate{
|
||||
Summary: "longTom",
|
||||
Stickers: map[string]string{strings.Repeat("1", 256): "val"},
|
||||
},
|
||||
err: errStickerTooLong,
|
||||
},
|
||||
{
|
||||
name: "sticker val too long",
|
||||
input: AnnotationCreate{
|
||||
Summary: "longTom",
|
||||
Stickers: map[string]string{"key": strings.Repeat("1", 256)},
|
||||
},
|
||||
err: errStickerTooLong,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := test.input.Validate(nowFunc)
|
||||
if test.err != nil {
|
||||
require.Equal(t, test.err, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.expected, test.input)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteFilter(t *testing.T) {
|
||||
type tst struct {
|
||||
name string
|
||||
input AnnotationDeleteFilter
|
||||
expected AnnotationDeleteFilter
|
||||
err *errors.Error
|
||||
}
|
||||
|
||||
tests := []tst{
|
||||
{
|
||||
name: "minimum valid delete",
|
||||
input: AnnotationDeleteFilter{
|
||||
StreamTag: "default",
|
||||
EndTime: &testTime,
|
||||
StartTime: &testTime,
|
||||
},
|
||||
expected: AnnotationDeleteFilter{
|
||||
StreamTag: "default",
|
||||
EndTime: &testTime,
|
||||
StartTime: &testTime,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "full valid delete",
|
||||
input: AnnotationDeleteFilter{
|
||||
StreamTag: "default",
|
||||
Stickers: map[string]string{"product": "oss"},
|
||||
EndTime: &testTime,
|
||||
StartTime: &testTime,
|
||||
},
|
||||
expected: AnnotationDeleteFilter{
|
||||
StreamTag: "default",
|
||||
Stickers: map[string]string{"product": "oss"},
|
||||
EndTime: &testTime,
|
||||
StartTime: &testTime,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing stream tag",
|
||||
input: AnnotationDeleteFilter{
|
||||
Stickers: map[string]string{"product": "oss"},
|
||||
EndTime: &testTime,
|
||||
StartTime: &testTime,
|
||||
},
|
||||
err: errMissingStreamTagOrId,
|
||||
},
|
||||
{
|
||||
name: "missing start time",
|
||||
input: AnnotationDeleteFilter{
|
||||
StreamTag: "default",
|
||||
Stickers: map[string]string{"product": "oss"},
|
||||
EndTime: &testTime,
|
||||
},
|
||||
err: errMissingStartTime,
|
||||
},
|
||||
{
|
||||
name: "missing end time",
|
||||
input: AnnotationDeleteFilter{
|
||||
StreamTag: "default",
|
||||
Stickers: map[string]string{"product": "oss"},
|
||||
StartTime: &testTime,
|
||||
},
|
||||
err: errMissingEndTime,
|
||||
},
|
||||
{
|
||||
name: "end time before start create request",
|
||||
input: AnnotationDeleteFilter{
|
||||
StreamTag: "default",
|
||||
Stickers: map[string]string{"product": "oss"},
|
||||
EndTime: &testTime,
|
||||
StartTime: &testTime2,
|
||||
},
|
||||
err: errReversedTimes,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := test.input.Validate()
|
||||
if test.err != nil {
|
||||
require.Equal(t, test.err, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.expected, test.input)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotationListFilter(t *testing.T) {
|
||||
type tst struct {
|
||||
name string
|
||||
input AnnotationListFilter
|
||||
expected AnnotationListFilter
|
||||
checkValue bool
|
||||
err *errors.Error
|
||||
}
|
||||
|
||||
tests := []tst{
|
||||
{
|
||||
name: "minimum valid",
|
||||
input: AnnotationListFilter{
|
||||
BasicFilter: BasicFilter{
|
||||
EndTime: &testTime,
|
||||
StartTime: &testTime,
|
||||
},
|
||||
},
|
||||
expected: AnnotationListFilter{
|
||||
BasicFilter: BasicFilter{
|
||||
EndTime: &testTime,
|
||||
StartTime: &testTime,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty valid",
|
||||
input: AnnotationListFilter{},
|
||||
expected: AnnotationListFilter{
|
||||
BasicFilter: BasicFilter{
|
||||
EndTime: &testTime,
|
||||
StartTime: &testTime,
|
||||
},
|
||||
},
|
||||
checkValue: true,
|
||||
},
|
||||
{
|
||||
name: "invalid due to reversed times",
|
||||
input: AnnotationListFilter{
|
||||
BasicFilter: BasicFilter{
|
||||
EndTime: &testTime,
|
||||
StartTime: &testTime2,
|
||||
},
|
||||
},
|
||||
err: errReversedTimes,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := test.input.Validate(nowFunc)
|
||||
if test.err != nil {
|
||||
require.Equal(t, test.err, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
if test.checkValue {
|
||||
require.Equal(t, *test.expected.BasicFilter.StartTime, *test.expected.BasicFilter.EndTime)
|
||||
} else {
|
||||
require.Equal(t, test.expected, test.input)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamListFilter(t *testing.T) {
|
||||
type tst struct {
|
||||
name string
|
||||
input StreamListFilter
|
||||
expected StreamListFilter
|
||||
checkValue bool
|
||||
err *errors.Error
|
||||
}
|
||||
|
||||
tests := []tst{
|
||||
{
|
||||
name: "minimum valid",
|
||||
input: StreamListFilter{
|
||||
BasicFilter: BasicFilter{
|
||||
EndTime: &testTime,
|
||||
StartTime: &testTime,
|
||||
},
|
||||
},
|
||||
expected: StreamListFilter{
|
||||
BasicFilter: BasicFilter{
|
||||
EndTime: &testTime,
|
||||
StartTime: &testTime,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty valid",
|
||||
input: StreamListFilter{},
|
||||
expected: StreamListFilter{
|
||||
BasicFilter: BasicFilter{
|
||||
EndTime: &testTime,
|
||||
StartTime: &testTime,
|
||||
},
|
||||
},
|
||||
checkValue: true,
|
||||
},
|
||||
{
|
||||
name: "invalid due to reversed times",
|
||||
input: StreamListFilter{
|
||||
BasicFilter: BasicFilter{
|
||||
EndTime: &testTime,
|
||||
StartTime: &testTime2,
|
||||
},
|
||||
},
|
||||
err: errReversedTimes,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := test.input.Validate(nowFunc)
|
||||
if test.err != nil {
|
||||
require.Equal(t, test.err, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
if test.checkValue {
|
||||
require.Equal(t, *test.expected.BasicFilter.StartTime, *test.expected.BasicFilter.EndTime)
|
||||
} else {
|
||||
require.Equal(t, test.expected, test.input)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamIsValid(t *testing.T) {
|
||||
type tst struct {
|
||||
name string
|
||||
input Stream
|
||||
err *errors.Error
|
||||
}
|
||||
|
||||
tests := []tst{
|
||||
{
|
||||
name: "minimum valid",
|
||||
input: Stream{
|
||||
Name: "default",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty valid",
|
||||
input: Stream{},
|
||||
},
|
||||
{
|
||||
name: "invalid name too long",
|
||||
input: Stream{
|
||||
Name: strings.Repeat("a", 512),
|
||||
},
|
||||
err: errStreamNameTooLong,
|
||||
},
|
||||
{
|
||||
name: "invalid description too long",
|
||||
input: Stream{
|
||||
Name: "longTom",
|
||||
Description: strings.Repeat("a", 2048),
|
||||
},
|
||||
err: errStreamDescTooLong,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if test.err != nil {
|
||||
require.Equal(t, test.err, test.input.Validate(false))
|
||||
} else {
|
||||
require.NoError(t, test.input.Validate(false))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicStreamIsValid(t *testing.T) {
|
||||
type tst struct {
|
||||
name string
|
||||
input BasicStream
|
||||
expected bool
|
||||
}
|
||||
|
||||
tests := []tst{
|
||||
{
|
||||
name: "minimum valid",
|
||||
input: BasicStream{
|
||||
Names: []string{"default"},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
input: BasicStream{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "empty name",
|
||||
input: BasicStream{Names: []string{""}},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require.Equal(t, test.expected, test.input.IsValid())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMashallReadAnnotations(t *testing.T) {
|
||||
ra := ReadAnnotations{
|
||||
"default": []ReadAnnotation{
|
||||
{
|
||||
ID: *annID,
|
||||
Summary: "this is one annotation",
|
||||
Stickers: map[string]string{"env": "testing"},
|
||||
StartTime: testTime.Format(time.RFC3339Nano),
|
||||
EndTime: testTime2.Format(time.RFC3339Nano),
|
||||
},
|
||||
{
|
||||
ID: *annID,
|
||||
Summary: "this is another annotation",
|
||||
Stickers: map[string]string{"env": "testing"},
|
||||
StartTime: testTime.Format(time.RFC3339Nano),
|
||||
EndTime: testTime.Format(time.RFC3339Nano),
|
||||
},
|
||||
},
|
||||
"testing": []ReadAnnotation{
|
||||
{
|
||||
ID: *annID,
|
||||
Summary: "this is yet another annotation",
|
||||
Stickers: map[string]string{"env": "testing"},
|
||||
StartTime: testTime.Format(time.RFC3339Nano),
|
||||
EndTime: testTime.Format(time.RFC3339Nano),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b, err := json.Marshal(ra)
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(b), 0)
|
||||
}
|
||||
|
||||
func TestSetStickerIncludes(t *testing.T) {
|
||||
type tst struct {
|
||||
name string
|
||||
input map[string][]string
|
||||
expected map[string]string
|
||||
}
|
||||
|
||||
tests := []tst{
|
||||
{
|
||||
name: "with stickerIncludes",
|
||||
input: map[string][]string{
|
||||
"stickerIncludes[product]": {"oss"},
|
||||
"stickerIncludes[author]": {"russ"},
|
||||
"streams": {"default", "blogs"},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"product": "oss",
|
||||
"author": "russ",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no sticker includes",
|
||||
input: map[string][]string{
|
||||
"startTime": {"2021-01-13T22%3A17%3A37.953Z"},
|
||||
"endTime": {"2021-01-13T22%3A17%3A37.953Z"},
|
||||
"streams": {"default", "blogs"},
|
||||
},
|
||||
expected: map[string]string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
f := AnnotationListFilter{}
|
||||
f.SetStickerIncludes(test.input)
|
||||
require.Equal(t, test.expected, f.StickerIncludes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetStickers(t *testing.T) {
|
||||
type tst struct {
|
||||
name string
|
||||
input map[string][]string
|
||||
expected map[string]string
|
||||
}
|
||||
|
||||
tests := []tst{
|
||||
{
|
||||
name: "with stickers",
|
||||
input: map[string][]string{
|
||||
"stickers[product]": {"oss"},
|
||||
"stickers[author]": {"russ"},
|
||||
"streams": {"default", "blogs"},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"product": "oss",
|
||||
"author": "russ",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no stickers",
|
||||
input: map[string][]string{
|
||||
"startTime": {"2021-01-13T22%3A17%3A37.953Z"},
|
||||
"endTime": {"2021-01-13T22%3A17%3A37.953Z"},
|
||||
"streams": {"default", "blogs"},
|
||||
},
|
||||
expected: map[string]string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
f := AnnotationDeleteFilter{}
|
||||
f.SetStickers(test.input)
|
||||
require.Equal(t, test.expected, f.Stickers)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: annotation.go
|
||||
|
||||
// Package mock is a generated GoMock package.
|
||||
package mock
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
influxdb "github.com/influxdata/influxdb/v2"
|
||||
platform "github.com/influxdata/influxdb/v2/kit/platform"
|
||||
)
|
||||
|
||||
// MockAnnotationService is a mock of AnnotationService interface.
|
||||
type MockAnnotationService struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockAnnotationServiceMockRecorder
|
||||
}
|
||||
|
||||
// MockAnnotationServiceMockRecorder is the mock recorder for MockAnnotationService.
|
||||
type MockAnnotationServiceMockRecorder struct {
|
||||
mock *MockAnnotationService
|
||||
}
|
||||
|
||||
// NewMockAnnotationService creates a new mock instance.
|
||||
func NewMockAnnotationService(ctrl *gomock.Controller) *MockAnnotationService {
|
||||
mock := &MockAnnotationService{ctrl: ctrl}
|
||||
mock.recorder = &MockAnnotationServiceMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockAnnotationService) EXPECT() *MockAnnotationServiceMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// CreateAnnotations mocks base method.
|
||||
func (m *MockAnnotationService) CreateAnnotations(ctx context.Context, orgID platform.ID, create []influxdb.AnnotationCreate) ([]influxdb.AnnotationEvent, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateAnnotations", ctx, orgID, create)
|
||||
ret0, _ := ret[0].([]influxdb.AnnotationEvent)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateAnnotations indicates an expected call of CreateAnnotations.
|
||||
func (mr *MockAnnotationServiceMockRecorder) CreateAnnotations(ctx, orgID, create interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAnnotations", reflect.TypeOf((*MockAnnotationService)(nil).CreateAnnotations), ctx, orgID, create)
|
||||
}
|
||||
|
||||
// CreateOrUpdateStream mocks base method.
|
||||
func (m *MockAnnotationService) CreateOrUpdateStream(ctx context.Context, orgID platform.ID, stream influxdb.Stream) (*influxdb.ReadStream, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateOrUpdateStream", ctx, orgID, stream)
|
||||
ret0, _ := ret[0].(*influxdb.ReadStream)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateOrUpdateStream indicates an expected call of CreateOrUpdateStream.
|
||||
func (mr *MockAnnotationServiceMockRecorder) CreateOrUpdateStream(ctx, orgID, stream interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdateStream", reflect.TypeOf((*MockAnnotationService)(nil).CreateOrUpdateStream), ctx, orgID, stream)
|
||||
}
|
||||
|
||||
// DeleteAnnotation mocks base method.
|
||||
func (m *MockAnnotationService) DeleteAnnotation(ctx context.Context, orgID, id platform.ID) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteAnnotation", ctx, orgID, id)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteAnnotation indicates an expected call of DeleteAnnotation.
|
||||
func (mr *MockAnnotationServiceMockRecorder) DeleteAnnotation(ctx, orgID, id interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAnnotation", reflect.TypeOf((*MockAnnotationService)(nil).DeleteAnnotation), ctx, orgID, id)
|
||||
}
|
||||
|
||||
// DeleteAnnotations mocks base method.
|
||||
func (m *MockAnnotationService) DeleteAnnotations(ctx context.Context, orgID platform.ID, delete influxdb.AnnotationDeleteFilter) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteAnnotations", ctx, orgID, delete)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteAnnotations indicates an expected call of DeleteAnnotations.
|
||||
func (mr *MockAnnotationServiceMockRecorder) DeleteAnnotations(ctx, orgID, delete interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAnnotations", reflect.TypeOf((*MockAnnotationService)(nil).DeleteAnnotations), ctx, orgID, delete)
|
||||
}
|
||||
|
||||
// DeleteStream mocks base method.
|
||||
func (m *MockAnnotationService) DeleteStream(ctx context.Context, orgID platform.ID, streamName string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteStream", ctx, orgID, streamName)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteStream indicates an expected call of DeleteStream.
|
||||
func (mr *MockAnnotationServiceMockRecorder) DeleteStream(ctx, orgID, streamName interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteStream", reflect.TypeOf((*MockAnnotationService)(nil).DeleteStream), ctx, orgID, streamName)
|
||||
}
|
||||
|
||||
// DeleteStreamByID mocks base method.
|
||||
func (m *MockAnnotationService) DeleteStreamByID(ctx context.Context, orgID, id platform.ID) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteStreamByID", ctx, orgID, id)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteStreamByID indicates an expected call of DeleteStreamByID.
|
||||
func (mr *MockAnnotationServiceMockRecorder) DeleteStreamByID(ctx, orgID, id interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteStreamByID", reflect.TypeOf((*MockAnnotationService)(nil).DeleteStreamByID), ctx, orgID, id)
|
||||
}
|
||||
|
||||
// GetAnnotation mocks base method.
|
||||
func (m *MockAnnotationService) GetAnnotation(ctx context.Context, orgID, id platform.ID) (*influxdb.AnnotationEvent, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAnnotation", ctx, orgID, id)
|
||||
ret0, _ := ret[0].(*influxdb.AnnotationEvent)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAnnotation indicates an expected call of GetAnnotation.
|
||||
func (mr *MockAnnotationServiceMockRecorder) GetAnnotation(ctx, orgID, id interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAnnotation", reflect.TypeOf((*MockAnnotationService)(nil).GetAnnotation), ctx, orgID, id)
|
||||
}
|
||||
|
||||
// ListAnnotations mocks base method.
|
||||
func (m *MockAnnotationService) ListAnnotations(ctx context.Context, orgID platform.ID, filter influxdb.AnnotationListFilter) (influxdb.ReadAnnotations, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ListAnnotations", ctx, orgID, filter)
|
||||
ret0, _ := ret[0].(influxdb.ReadAnnotations)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ListAnnotations indicates an expected call of ListAnnotations.
|
||||
func (mr *MockAnnotationServiceMockRecorder) ListAnnotations(ctx, orgID, filter interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAnnotations", reflect.TypeOf((*MockAnnotationService)(nil).ListAnnotations), ctx, orgID, filter)
|
||||
}
|
||||
|
||||
// ListStreams mocks base method.
|
||||
func (m *MockAnnotationService) ListStreams(ctx context.Context, orgID platform.ID, filter influxdb.StreamListFilter) ([]influxdb.ReadStream, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ListStreams", ctx, orgID, filter)
|
||||
ret0, _ := ret[0].([]influxdb.ReadStream)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ListStreams indicates an expected call of ListStreams.
|
||||
func (mr *MockAnnotationServiceMockRecorder) ListStreams(ctx, orgID, filter interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListStreams", reflect.TypeOf((*MockAnnotationService)(nil).ListStreams), ctx, orgID, filter)
|
||||
}
|
||||
|
||||
// UpdateAnnotation mocks base method.
|
||||
func (m *MockAnnotationService) UpdateAnnotation(ctx context.Context, orgID, id platform.ID, update influxdb.AnnotationCreate) (*influxdb.AnnotationEvent, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateAnnotation", ctx, orgID, id, update)
|
||||
ret0, _ := ret[0].(*influxdb.AnnotationEvent)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateAnnotation indicates an expected call of UpdateAnnotation.
|
||||
func (mr *MockAnnotationServiceMockRecorder) UpdateAnnotation(ctx, orgID, id, update interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAnnotation", reflect.TypeOf((*MockAnnotationService)(nil).UpdateAnnotation), ctx, orgID, id, update)
|
||||
}
|
||||
|
||||
// UpdateStream mocks base method.
|
||||
func (m *MockAnnotationService) UpdateStream(ctx context.Context, orgID, id platform.ID, stream influxdb.Stream) (*influxdb.ReadStream, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateStream", ctx, orgID, id, stream)
|
||||
ret0, _ := ret[0].(*influxdb.ReadStream)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateStream indicates an expected call of UpdateStream.
|
||||
func (mr *MockAnnotationServiceMockRecorder) UpdateStream(ctx, orgID, id, stream interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStream", reflect.TypeOf((*MockAnnotationService)(nil).UpdateStream), ctx, orgID, id, stream)
|
||||
}
|
Loading…
Reference in New Issue