Skip to content

Commit 5b6a3a4

Browse files
committed
Reusable mock for events
Migrates the tests for events to use a proper events mock. There is a workaround here for an old, old possibly bug in the events API, but it's a little fragile, and not really that important to test anymore.
1 parent 13177ba commit 5b6a3a4

File tree

4 files changed

+339
-184
lines changed

4 files changed

+339
-184
lines changed

Cognite.Testing/Mock/Events.cs

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Net.Http;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Cognite.Extractor.Common;
8+
using CogniteSdk;
9+
using Moq;
10+
using Xunit;
11+
12+
namespace Cognite.Extractor.Testing.Mock
13+
{
14+
/// <summary>
15+
/// Mock implementation of the Events API.
16+
/// </summary>
17+
public class EventsMock
18+
{
19+
private long _nextId = 10000;
20+
private readonly Dictionary<string, Event> _eventsByExternalId = new Dictionary<string, Event>();
21+
private readonly Dictionary<long, Event> _eventsById = new Dictionary<long, Event>();
22+
23+
/// <summary>
24+
/// All mocked events.
25+
/// </summary>
26+
public ICollection<Event> Events => _eventsById.Values;
27+
28+
29+
/// <summary>
30+
/// Mock an event, assigning it an ID.
31+
/// </summary>
32+
/// <param name="ev">Event to add.</param>
33+
public void MockEvent(Event ev)
34+
{
35+
if (ev == null) throw new ArgumentNullException(nameof(ev));
36+
ev.Id = _nextId++;
37+
_eventsById[ev.Id] = ev;
38+
if (!string.IsNullOrEmpty(ev.ExternalId))
39+
{
40+
_eventsByExternalId[ev.ExternalId] = ev;
41+
}
42+
}
43+
44+
/// <summary>
45+
/// Remove a mocked event by its external ID, if it is present.
46+
/// </summary>
47+
/// <param name="externalId">External ID of event to remove.</param>
48+
public bool Remove(string externalId)
49+
{
50+
if (_eventsByExternalId.TryGetValue(externalId, out var ev))
51+
{
52+
_eventsByExternalId.Remove(externalId);
53+
_eventsById.Remove(ev.Id);
54+
return true;
55+
}
56+
return false;
57+
}
58+
59+
/// <summary>
60+
/// Mock an event with the given external ID, assigning it an ID.
61+
/// </summary>
62+
/// <param name="externalId">External ID of the event to add.</param>
63+
public void MockEvent(string externalId)
64+
{
65+
MockEvent(new Event
66+
{
67+
ExternalId = externalId,
68+
Type = "someType",
69+
StartTime = DateTime.UtcNow.ToUnixTimeMilliseconds(),
70+
EndTime = DateTime.UtcNow.ToUnixTimeMilliseconds(),
71+
CreatedTime = DateTime.UtcNow.ToUnixTimeMilliseconds(),
72+
LastUpdatedTime = DateTime.UtcNow.ToUnixTimeMilliseconds(),
73+
});
74+
}
75+
76+
/// <summary>
77+
/// Get an event by its identity, if it exists.
78+
/// </summary>
79+
/// <param name="id">Event ID</param>
80+
/// <returns>The event, if it exists.</returns>
81+
public Event? GetEvent(Identity id)
82+
{
83+
if (id == null) throw new ArgumentNullException(nameof(id));
84+
85+
if (id.Id.HasValue && _eventsById.TryGetValue(id.Id.Value, out var ev))
86+
{
87+
return ev;
88+
}
89+
else if (!string.IsNullOrEmpty(id.ExternalId) && _eventsByExternalId.TryGetValue(id.ExternalId, out var ev2))
90+
{
91+
return ev2;
92+
}
93+
94+
return null;
95+
}
96+
/// <summary>
97+
/// Get an event by its external ID, if it exists.
98+
/// </summary>
99+
/// <param name="externalId">Event external ID</param>
100+
/// <returns>The event, if it exists.</returns>
101+
102+
public Event? GetEvent(string externalId)
103+
{
104+
if (externalId == null) throw new ArgumentNullException(nameof(externalId));
105+
106+
if (_eventsByExternalId.TryGetValue(externalId, out var ev))
107+
{
108+
return ev;
109+
}
110+
111+
return null;
112+
}
113+
114+
/// <summary>
115+
/// Get an event by its internal ID, if it exists.
116+
/// </summary>
117+
/// <param name="id">Event ID</param>
118+
/// <returns>The event, if it exists.</returns>
119+
public Event? GetEvent(long id)
120+
{
121+
if (_eventsById.TryGetValue(id, out var ev))
122+
{
123+
return ev;
124+
}
125+
126+
return null;
127+
}
128+
129+
130+
/// <summary>
131+
/// Clear the events mock, removing all mocked events.
132+
/// </summary>
133+
public void Clear()
134+
{
135+
_nextId = 10000;
136+
_eventsByExternalId.Clear();
137+
_eventsById.Clear();
138+
}
139+
140+
/// <summary>
141+
/// Get a matcher for the /events/byids endpoint.
142+
/// </summary>
143+
/// <param name="times">Expected number of executions.</param>
144+
public RequestMatcher MakeGetByIdsMatcher(Times times)
145+
{
146+
return new SimpleMatcher("POST", "/events/byids", EventsByIdsImpl, times);
147+
}
148+
149+
/// <summary>
150+
/// Get a matcher for the /events endpoint for creating events.
151+
/// </summary>
152+
/// <param name="times">Expected number of executions.</param>
153+
public RequestMatcher MakeCreateEventsMatcher(Times times)
154+
{
155+
return new SimpleMatcher("POST", "/events$", EventsCreateImpl, times);
156+
}
157+
158+
private async Task<HttpResponseMessage> EventsCreateImpl(RequestContext context, CancellationToken token)
159+
{
160+
var events = await context.ReadJsonBody<ItemsWithoutCursor<EventCreate>>().ConfigureAwait(false);
161+
Assert.NotNull(events);
162+
var created = new List<Event>();
163+
var conflict = new List<string>();
164+
165+
foreach (var ev in events.Items)
166+
{
167+
if (ev.ExternalId != null && _eventsByExternalId.ContainsKey(ev.ExternalId))
168+
{
169+
conflict.Add(ev.ExternalId);
170+
continue;
171+
}
172+
173+
var newEvent = new Event
174+
{
175+
Id = _nextId++,
176+
ExternalId = ev.ExternalId,
177+
Type = ev.Type,
178+
StartTime = ev.StartTime,
179+
EndTime = ev.EndTime,
180+
Source = ev.Source,
181+
Description = ev.Description,
182+
CreatedTime = DateTime.UtcNow.ToUnixTimeMilliseconds(),
183+
LastUpdatedTime = DateTime.UtcNow.ToUnixTimeMilliseconds(),
184+
Metadata = ev.Metadata,
185+
};
186+
187+
_eventsById[newEvent.Id] = newEvent;
188+
if (newEvent.ExternalId != null)
189+
{
190+
_eventsByExternalId[newEvent.ExternalId] = newEvent;
191+
}
192+
created.Add(newEvent);
193+
}
194+
195+
if (conflict.Count > 0)
196+
{
197+
return context.CreateError(new CogniteError
198+
{
199+
Code = 409,
200+
Message = "Conflict",
201+
Duplicated = conflict.Distinct().Select(id => MockUtils.ToMultiValueDict(new Identity(id))).ToList()
202+
});
203+
}
204+
205+
return context.CreateJsonResponse(new ItemsWithoutCursor<Event> { Items = created });
206+
}
207+
208+
private async Task<HttpResponseMessage> EventsByIdsImpl(RequestContext context, CancellationToken token)
209+
{
210+
var ids = await context.ReadJsonBody<ItemsWithIgnoreUnknownIds<RawIdentity>>().ConfigureAwait(false);
211+
Assert.NotNull(ids);
212+
var found = new List<Event>();
213+
var missing = new List<Identity>();
214+
215+
foreach (var id in ids.Items)
216+
{
217+
Event? ev = null;
218+
if (id.Id.HasValue)
219+
{
220+
_eventsById.TryGetValue(id.Id.Value, out ev);
221+
}
222+
else if (!string.IsNullOrEmpty(id.ExternalId))
223+
{
224+
_eventsByExternalId.TryGetValue(id.ExternalId, out ev);
225+
}
226+
227+
if (ev != null)
228+
{
229+
found.Add(ev);
230+
}
231+
else
232+
{
233+
missing.Add(id.ToIdentity());
234+
}
235+
}
236+
237+
if (!ids.IgnoreUnknownIds && missing.Count > 0)
238+
{
239+
return context.CreateError(new CogniteError
240+
{
241+
Code = 400,
242+
Message = "Events not found",
243+
Missing = missing.Distinct().Select(MockUtils.ToMultiValueDict).ToList(),
244+
});
245+
}
246+
247+
return context.CreateJsonResponse(new ItemsWithoutCursor<Event>
248+
{
249+
Items = found
250+
});
251+
}
252+
}
253+
}

Cognite.Testing/Mock/MockUtils.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using CogniteSdk;
6+
7+
namespace Cognite.Extractor.Testing.Mock
8+
{
9+
/// <summary>
10+
/// Common utilities for mock implementations of CDF APIs.
11+
/// </summary>
12+
public class MockUtils
13+
{
14+
/// <summary>
15+
/// Convert an Identity to a dictionary used in error responses.
16+
/// </summary>
17+
/// <param name="id">Identity to convert</param>
18+
/// <returns>Multivalue dictionary</returns>
19+
public static Dictionary<string, MultiValue> ToMultiValueDict(Identity id)
20+
{
21+
if (id == null) throw new ArgumentNullException(nameof(id));
22+
var dict = new Dictionary<string, MultiValue>();
23+
if (id.Id.HasValue)
24+
{
25+
dict["id"] = MultiValue.Create(id.Id.Value);
26+
}
27+
else if (id.InstanceId != null)
28+
{
29+
dict["instanceId"] = MultiValue.Create(id.InstanceId);
30+
}
31+
else if (!string.IsNullOrEmpty(id.ExternalId))
32+
{
33+
dict["externalId"] = MultiValue.Create(id.ExternalId);
34+
}
35+
return dict;
36+
}
37+
}
38+
}

Cognite.Testing/Mock/TimeSeries.cs

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -197,24 +197,6 @@ public RequestMatcher MakeCreateDatapointsMatcher(Times times)
197197
return new SimpleMatcher("POST", "/timeseries/data", CreateDatapointsImpl, times);
198198
}
199199

200-
private static Dictionary<string, MultiValue> ToMultiValueDict(Identity id)
201-
{
202-
var dict = new Dictionary<string, MultiValue>();
203-
if (id.Id.HasValue)
204-
{
205-
dict["id"] = MultiValue.Create(id.Id.Value);
206-
}
207-
else if (id.InstanceId != null)
208-
{
209-
dict["instanceId"] = MultiValue.Create(id.InstanceId);
210-
}
211-
else if (!string.IsNullOrEmpty(id.ExternalId))
212-
{
213-
dict["externalId"] = MultiValue.Create(id.ExternalId);
214-
}
215-
return dict;
216-
}
217-
218200
private async Task<HttpResponseMessage> TimeSeriesByIdsImpl(RequestContext context, CancellationToken token)
219201
{
220202
var ids = await context.ReadJsonBody<ItemsWithIgnoreUnknownIds<RawIdentity>>().ConfigureAwait(false);
@@ -240,7 +222,7 @@ private async Task<HttpResponseMessage> TimeSeriesByIdsImpl(RequestContext conte
240222
{
241223
Code = 400,
242224
Message = "Timeseries not found",
243-
Missing = missing.Distinct().Select(ToMultiValueDict).ToList(),
225+
Missing = missing.Distinct().Select(MockUtils.ToMultiValueDict).ToList(),
244226
});
245227
}
246228
return context.CreateJsonResponse(new ItemsWithoutCursor<TimeSeries>
@@ -302,7 +284,7 @@ private async Task<HttpResponseMessage> CreateDatapointsImpl(RequestContext cont
302284
{
303285
Code = 400,
304286
Message = "Timeseries not found",
305-
Missing = missing.Distinct().Select(ToMultiValueDict).ToList(),
287+
Missing = missing.Distinct().Select(MockUtils.ToMultiValueDict).ToList(),
306288
});
307289
}
308290
if (mismatchedExpected != null)

0 commit comments

Comments
 (0)