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         uint id = 10_000;
68         R inner;
69 
70         auto empty () {
71             if(inner.empty) return true;
72             if(id < 2)      return true;
73             return false;
74         }
75 
76         auto front() {
77             auto ans = inner.front;
78             ans["id"] = Json(id);
79             // DateTime has no timezone, we pretend it is UTC after cast
80             ans["published_timestamp"] = Json(date.toISOExtString ~ "Z");
81             return ans;
82         }
83 
84         auto popFront() {
85             inner.popFront();
86             id--;
87             date -= dur!"days"(uniform(1,12));
88         }
89     }
90 
91     DateSequencing ds;
92     ds.date = cast(DateTime) newestDate;
93     ds.inner = articles;
94     return ds;
95 } unittest {
96     import std.algorithm : equal, map, isSorted;
97     auto seq = generate!fakeArticleMe.seqence(Clock.currTime).take(3);
98     assert(seq.map!(x => x["id"].get!uint).equal([10_000, 9_999, 9_998]));
99     assert(seq.map!(x => x["published_timestamp"].get!string).array.isSorted!"a > b");
100 }