Skip to content

Commit f4b8730

Browse files
authored
[TSD-18] Add simple tests (#14)
* [CO-289] Add simple tests, pending tests. * [CO-289] Working first version. * [CO-289] Working simple tests, ongoing refactoring to try to increase testability. * [CO-289] Working simple tests, along with top level functions. * [CO-289] Fixed review comments.
1 parent 3e9b8b8 commit f4b8730

File tree

5 files changed

+350
-73
lines changed

5 files changed

+350
-73
lines changed

log-classifier.cabal

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ library
4242
, text
4343
, universum
4444
, zip-archive
45+
, QuickCheck
4546

4647
default-language: Haskell2010
4748
default-extensions: TypeOperators
@@ -67,7 +68,8 @@ library
6768
executable log-classifier-exe
6869
hs-source-dirs: app
6970
main-is: Main.hs
70-
ghc-options: -threaded -rtsopts -with-rtsopts=-N
71+
ghc-options: -threaded
72+
-rtsopts -with-rtsopts=-N
7173
-Wall
7274
build-depends: base >=4.7 && <5
7375
, log-classifier
@@ -99,7 +101,6 @@ test-suite log-classifier-test
99101
type: exitcode-stdio-1.0
100102
main-is: Spec.hs
101103
ghc-options: -Wall
102-
other-modules: Paths_log_classifier
103104
build-depends: aeson
104105
, array
105106
, attoparsec
@@ -114,6 +115,8 @@ test-suite log-classifier-test
114115
, text
115116
, universum
116117
, zip-archive
118+
, QuickCheck
119+
, hspec
117120

118121
default-language: Haskell2010
119122
default-extensions: TypeOperators
@@ -131,6 +134,6 @@ test-suite log-classifier-test
131134
TemplateHaskell
132135
RecordWildCards
133136
ScopedTypeVariables
134-
UndecidableInstances
135137
MonadFailDesugaring
136138
TupleSections
139+

src/Lib.hs

Lines changed: 77 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ module Lib
99
, processTickets
1010
, fetchTickets
1111
, showStatistics
12+
13+
, listAndSortTickets
1214
) where
1315

1416
import Universum
@@ -23,10 +25,11 @@ import LogAnalysis.Classifier (extractErrorCodes, extractIssuesFromLog
2325
import LogAnalysis.KnowledgeCSVParser (parseKnowLedgeBase)
2426
import LogAnalysis.Types (ErrorCode (..), Knowledge, renderErrorCode, setupAnalysis)
2527
import Util (extractLogsFromZip)
26-
import Zendesk (App, Attachment (..), Comment (..), Config (..), RequestType (..),
27-
TicketId, TicketInfo (..), TicketTag (..), ZendeskLayer (..),
28-
ZendeskResponse (..), asksZendeskLayer, assignToPath, defaultConfig,
29-
knowledgebasePath, renderTicketStatus, runApp, tokenPath)
28+
import Zendesk (App, Attachment (..), Comment (..), Config (..), IOLayer (..),
29+
RequestType (..), TicketId, TicketInfo (..), TicketTag (..),
30+
ZendeskLayer (..), ZendeskResponse (..), asksIOLayer, asksZendeskLayer,
31+
assignToPath, defaultConfig, knowledgebasePath, renderTicketStatus,
32+
runApp, tokenPath)
3033

3134
------------------------------------------------------------
3235
-- Functions
@@ -53,8 +56,8 @@ runZendeskMain = do
5356
-- At this point, the configuration is set up and there is no point in using a pure IO.
5457
case args of
5558
CollectEmails -> runApp collectEmails cfg
56-
(ProcessTicket ticketId) -> runApp (processTicket ticketId) cfg
57-
ProcessTickets -> runApp processTickets cfg
59+
(ProcessTicket ticketId) -> void $ runApp (processTicket ticketId) cfg
60+
ProcessTickets -> void $ runApp processTickets cfg
5861
FetchTickets -> runApp fetchTickets cfg
5962
ShowStatistics -> runApp showStatistics cfg
6063

@@ -70,65 +73,80 @@ collectEmails = do
7073
let ticketIds = foldr (\TicketInfo{..} acc -> ticketId : acc) [] tickets
7174
mapM_ extractEmailAddress ticketIds
7275

73-
processTicket :: TicketId -> App ()
76+
77+
processTicket :: TicketId -> App [ZendeskResponse]
7478
processTicket ticketId = do
79+
7580
-- We first fetch the function from the configuration
76-
getTicketInfo <- asksZendeskLayer zlGetTicketInfo
77-
putTextLn "Processing single ticket"
78-
ticketInfo <- getTicketInfo ticketId
79-
processTicketAndId ticketInfo
80-
putTextLn "Process finished, please see the following url"
81-
putTextLn $ "https://iohk.zendesk.com/agent/tickets/" <> show ticketId
81+
getTicketInfo <- asksZendeskLayer zlGetTicketInfo
82+
printText <- asksIOLayer iolPrintText
83+
84+
printText "Processing single ticket"
85+
86+
ticketInfo <- getTicketInfo ticketId
87+
attachments <- getTicketAttachments ticketInfo
88+
89+
zendeskResponse <- mapM (inspectAttachment ticketInfo) attachments
90+
91+
postTicketComment <- asksZendeskLayer zlPostTicketComment
92+
_ <- mapM postTicketComment zendeskResponse
93+
94+
printText "Process finished, please see the following url"
95+
printText $ "https://iohk.zendesk.com/agent/tickets/" <> show ticketId
96+
97+
pure zendeskResponse
98+
8299

83100
processTickets :: App ()
84101
processTickets = do
85-
cfg <- ask
86-
sortedTicketIds <- processBatchTickets cfg
87-
mapM_ processTicketAndId sortedTicketIds
102+
sortedTicketIds <- listAndSortTickets
103+
104+
_ <- mapM (processTicket . ticketId) sortedTicketIds
105+
88106
putTextLn "All the tickets has been processed."
89107

108+
90109
fetchTickets :: App ()
91110
fetchTickets = do
92-
cfg <- ask
93-
sortedTicketIds <- processBatchTickets cfg
111+
sortedTicketIds <- listAndSortTickets
94112
mapM_ (putTextLn . show) sortedTicketIds
95113
putTextLn "All the tickets has been processed."
96114

115+
97116
showStatistics :: App ()
98117
showStatistics = do
99118
cfg <- ask
100119
-- We first fetch the function from the configuration
101120
listTickets <- asksZendeskLayer zlListTickets
102121

103122
putTextLn $ "Classifier is going to gather ticket information assigned to: " <> cfgEmail cfg
104-
liftIO printWarning
105-
tickets <- listTickets Assigned
123+
124+
tickets <- listTickets Assigned
106125
liftIO $ printTicketCountMessage tickets (cfgEmail cfg)
107126

108127

109-
processBatchTickets :: Config -> App [TicketInfo]
110-
processBatchTickets cfg = do
128+
listAndSortTickets :: App [TicketInfo]
129+
listAndSortTickets = do
130+
131+
Config{..} <- ask
111132

112133
-- We first fetch the function from the configuration
113134
listTickets <- asksZendeskLayer zlListTickets
135+
printText <- asksIOLayer iolPrintText
136+
137+
printText $ "Classifier is going to process tickets assign to: " <> cfgEmail
114138

115-
putTextLn $ "Classifier is going to process tickets assign to: " <> cfgEmail cfg
116-
liftIO printWarning
117-
tickets <- listTickets Assigned
139+
tickets <- listTickets Assigned
118140

119141
let filteredTicketIds = filterAnalyzedTickets tickets
120142
let sortedTicketIds = sortBy compare filteredTicketIds
121143

122-
putTextLn $ "There are " <> show (length sortedTicketIds) <> " unanalyzed tickets."
123-
putTextLn "Processing tickets, this may take hours to finish."
144+
printText $ "There are " <> show (length sortedTicketIds) <> " unanalyzed tickets."
145+
printText "Processing tickets, this may take hours to finish."
124146

125147
pure sortedTicketIds
126148

127149

128-
-- | Warning
129-
printWarning :: IO ()
130-
printWarning = putTextLn "Note that this process may take a while. Please do not kill the process"
131-
132150
-- | Print how many tickets are assinged, analyzed, and unanalyzed
133151
printTicketCountMessage :: [TicketInfo] -> Text -> IO ()
134152
printTicketCountMessage tickets email = do
@@ -177,36 +195,42 @@ extractEmailAddress ticketId = do
177195
liftIO $ appendFile "emailAddress.txt" (emailAddress <> "\n")
178196
liftIO $ putTextLn emailAddress
179197

198+
180199
-- | Process specifig ticket id (can be used for testing) only inspects the one's with logs
181-
processTicketAndId :: TicketInfo -> App ()
182-
processTicketAndId ticketInfo@TicketInfo{..} = do
200+
-- TODO(ks): Switch to `(MonadReader Config m)`, pure function?
201+
getTicketAttachments :: TicketInfo -> App [Attachment]
202+
getTicketAttachments TicketInfo{..} = do
183203

184-
getTicketComments <- asksZendeskLayer zlGetTicketComments
204+
-- Get the function from the configuration
205+
getTicketComments <- asksZendeskLayer zlGetTicketComments
206+
comments <- getTicketComments ticketId
207+
208+
-- However, if we want this to be more composable...
209+
pure $ getAttachmentsFromComment comments
185210

186-
comments <- getTicketComments ticketId
187211

212+
-- | A pure function for fetching @Attachment@ from @Comment@.
213+
getAttachmentsFromComment :: [Comment] -> [Attachment]
214+
getAttachmentsFromComment comments = do
188215
-- Filter tickets without logs
189-
-- Could analyze the comments but I don't see it useful..
190216
let commentsWithAttachments :: [Comment]
191-
commentsWithAttachments = filter (\x -> length (cAttachments x) > 0) comments
217+
commentsWithAttachments = filter commentHasAttachment comments
192218

193219
-- Filter out ticket without logs
194220
let attachments :: [Attachment]
195221
attachments = concatMap cAttachments commentsWithAttachments
196222

197-
let justLogs :: [Attachment]
198-
justLogs = filter (\x -> "application/zip" == aContentType x) attachments
223+
-- Filter out non-logs
224+
filter isAttachmentZip attachments
199225

200-
mapM_ (inspectAttachmentAndPostComment ticketInfo) justLogs
226+
where
227+
commentHasAttachment :: Comment -> Bool
228+
commentHasAttachment comment = length (cAttachments comment) > 0
201229

202-
-- | Inspect attachment then post comment to the ticket
203-
inspectAttachmentAndPostComment :: TicketInfo -> Attachment -> App ()
204-
inspectAttachmentAndPostComment ticketInfo attachment = do
205-
liftIO $ putTextLn $ "Analyzing ticket: " <> show ticketInfo
206-
zendeskResponse <- inspectAttachment ticketInfo attachment
230+
-- Readability
231+
isAttachmentZip :: Attachment -> Bool
232+
isAttachmentZip attachment = "application/zip" == aContentType attachment
207233

208-
postTicketComment <- asksZendeskLayer zlPostTicketComment
209-
postTicketComment zendeskResponse
210234

211235
-- | Given number of file of inspect, knowledgebase and attachment,
212236
-- analyze the logs and return the results.
@@ -217,17 +241,18 @@ inspectAttachmentAndPostComment ticketInfo attachment = do
217241
inspectAttachment :: TicketInfo -> Attachment -> App ZendeskResponse
218242
inspectAttachment ticketInfo@TicketInfo{..} att = do
219243

220-
Config{..} <- ask
244+
Config{..} <- ask
221245

222-
getAttachment <- asksZendeskLayer zlGetAttachment
246+
getAttachment <- asksZendeskLayer zlGetAttachment
247+
printText <- asksIOLayer iolPrintText
223248

224249
rawlog <- getAttachment att -- Get attachment
225250
let results = extractLogsFromZip cfgNumOfLogsToAnalyze rawlog
226251

227252
case results of
228253
Left _ -> do
229254

230-
liftIO . putStrLn . renderErrorCode $ SentLogCorrupted
255+
printText . renderErrorCode $ SentLogCorrupted
231256

232257
pure ZendeskResponse
233258
{ zrTicketId = ticketId
@@ -246,7 +271,7 @@ inspectAttachment ticketInfo@TicketInfo{..} att = do
246271

247272
let fErrorCode = foldr (\errorCode acc -> errorCode <> ";" <> acc) "" errorCodes
248273

249-
liftIO . putTextLn $ fErrorCode
274+
printText fErrorCode
250275

251276
pure ZendeskResponse
252277
{ zrTicketId = ticketId
@@ -257,7 +282,7 @@ inspectAttachment ticketInfo@TicketInfo{..} att = do
257282

258283
Left _ -> do
259284

260-
liftIO . putStrLn . renderTicketStatus $ NoKnownIssue
285+
printText . renderTicketStatus $ NoKnownIssue
261286

262287
pure ZendeskResponse
263288
{ zrTicketId = ticketId

src/Zendesk/Functions.hs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
module Zendesk.Functions
55
( basicZendeskLayer
6+
, emptyZendeskLayer
7+
, basicIOLayer
68
, defaultConfig
79
) where
810

@@ -16,10 +18,11 @@ import Network.HTTP.Simple (Request, addRequestHeader, getResponseBody
1618
parseRequest_, setRequestBasicAuth, setRequestBodyJSON,
1719
setRequestMethod, setRequestPath)
1820

19-
import Zendesk.Types (Attachment (..), Comment (..), Config (..), RequestType (..),
20-
Ticket (..), TicketId, TicketInfo (..), TicketList (..),
21-
TicketTag (..), ZendeskLayer (..), ZendeskResponse (..),
22-
parseAgentId, parseComments, parseTickets, renderTicketStatus)
21+
import Zendesk.Types (Attachment (..), Comment (..), Config (..), IOLayer (..),
22+
RequestType (..), Ticket (..), TicketId, TicketInfo (..),
23+
TicketList (..), TicketTag (..), ZendeskLayer (..),
24+
ZendeskResponse (..), parseAgentId, parseComments, parseTickets,
25+
renderTicketStatus)
2326

2427

2528
-- | The default configuration.
@@ -35,6 +38,7 @@ defaultConfig =
3538
, cfgNumOfLogsToAnalyze = 5
3639
, cfgIsCommentPublic = True -- TODO(ks): For now, we need this in CLI.
3740
, cfgZendeskLayer = basicZendeskLayer
41+
, cfgIOLayer = basicIOLayer
3842
}
3943

4044
-- | The basic Zendesk layer.
@@ -48,6 +52,24 @@ basicZendeskLayer = ZendeskLayer
4852
, zlGetTicketComments = getTicketComments
4953
}
5054

55+
basicIOLayer :: (MonadIO m, MonadReader Config m) => IOLayer m
56+
basicIOLayer = IOLayer
57+
{ iolPrintText = putTextLn
58+
, iolReadFile = \_ -> error "Not implemented readFile!"
59+
-- ^ TODO(ks): We need to implement this!
60+
}
61+
62+
-- | The non-implemented Zendesk layer.
63+
emptyZendeskLayer :: (MonadIO m, MonadReader Config m) => ZendeskLayer m
64+
emptyZendeskLayer = ZendeskLayer
65+
{ zlGetTicketInfo = \_ -> error "Not implemented zlGetTicketInfo!"
66+
, zlListTickets = \_ -> error "Not implemented zlListTickets!"
67+
, zlPostTicketComment = \_ -> error "Not implemented zlPostTicketComment!"
68+
, zlGetAgentId = pure 1
69+
, zlGetAttachment = \_ -> error "Not implemented zlGetAttachment!"
70+
, zlGetTicketComments = \_ -> error "Not implemented zlGetTicketComments!"
71+
}
72+
5173

5274
-- | Get single ticket info.
5375
getTicketInfo

0 commit comments

Comments
 (0)