1 module unittesting.articlegenerator;
2 
3 import std.datetime;
4 import std.random;
5 import std.range;
6 
7 import vibe.data.json;
8 
9 import devto.api;
10 
11 @safe:
12 
13 string articleTemplate = `{
14     "published_at":null,
15     "canonical_url":"https://dev.to/username/articleslug",
16     "comments_count":0,
17     "page_views_count":0,
18     "id":111111,
19     "published_timestamp":"2020-01-16T01:22:00Z",
20     "positive_reactions_count":0,
21     "published":false,
22     "body_markdown":"Text used in the article.",
23     "title":"Title of the Article",
24     "url":"https://dev.to/username/articleslug",
25     "user":{
26       "username":"username",
27       "profile_image":"https://res.cloudinary.com/practicaldev/SomeImage.png",
28       "github_username":"GitHubUserName",
29       "profile_image_90":"https://res.cloudinary.com/practicaldev/SomeImage.png",
30       "website_url":"https://www.example.com",
31       "twitter_username":null,
32       "name":"User Name"
33     },
34     "cover_image":null,
35     "slug":"articleslug",
36     "description":"",
37     "path":"/username/articleslug",
38     "tag_list":[
39       "testing"
40     ],
41     "type_of":"article"
42   }`;
43 
44 /**
45  * Provides an initial article template for testing.
46  *
47  * It is expected to be combined with std.range.generate and seqence.
48  *
49  * `generate!fakeArticleMe.seqence().take(3);`
50  */
51 auto fakeArticleMe() {
52     // Deep copy Json object
53     return articleTemplate.parseJsonString;
54 }
55 
56 /**
57  * Given a range of `fakeArticleMe()` apply a decending id and publish date
58  * to replicate the behavior of the API.
59  *
60  * Note: This does not provide any unpublished articles which are provided
61  * first in the API.
62  */
63 auto seqence(R)(R articles, const SysTime newestDate = Clock.currTime)
64             if(is(ElementType!R == Json)) {
65     struct DateSequencing {
66         DateTime date;
67         typeof(iota(3).randomCover) id;
68         R inner;
69 
70         this(typeof(id) ids) {
71             id = ids;
72         }
73 
74         auto empty () {
75             if(inner.empty) return true;
76             if(id.empty) return true;
77             return false;
78         }
79 
80         auto front() {
81             auto ans = inner.front;
82             ans["id"] = Json(id.front);
83             // DateTime has no timezone, we pretend it is UTC after cast
84             ans["published_timestamp"] = Json(date.toISOExtString ~ "Z");
85             ans["published"] = Json(true);
86             return ans;
87         }
88 
89         auto popFront() {
90             inner.popFront();
91             id.popFront;
92             date -= dur!"days"(uniform(1,12));
93         }
94     }
95 
96     DateSequencing ds = DateSequencing(iota(10_000).randomCover);
97     ds.date = cast(DateTime) newestDate;
98     ds.inner = articles;
99     return ds;
100 } unittest {
101     import std.algorithm : equal, map, isSorted;
102     auto seq = generate!fakeArticleMe.seqence(Clock.currTime).take(3);
103     assert(seq.map!(x => x["published_timestamp"].get!string).array.isSorted!"a > b");
104 }