logPersistence.mjs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. import { describe, it, before, after } from 'mocha'
  2. import { expect } from 'chai'
  3. import { By, Condition } from 'selenium-webdriver'
  4. import fs from 'fs'
  5. import path from 'path'
  6. import {
  7. getRootAndWait,
  8. takeScreenshotOnFailure,
  9. } from '../../lib/elements.js'
  10. describe('config: logPersistence', function () {
  11. const logsDir = '/tmp/olivetin-test-logs'
  12. let firstExecutionTrackingId = null
  13. before(async function () {
  14. // Clean up any existing test logs
  15. if (fs.existsSync(logsDir)) {
  16. fs.rmSync(logsDir, { recursive: true, force: true })
  17. }
  18. fs.mkdirSync(logsDir, { recursive: true })
  19. await runner.start('logPersistence')
  20. })
  21. after(async () => {
  22. await runner.stop()
  23. // Clean up test logs directory
  24. if (fs.existsSync(logsDir)) {
  25. fs.rmSync(logsDir, { recursive: true, force: true })
  26. }
  27. })
  28. afterEach(function () {
  29. takeScreenshotOnFailure(this.currentTest, webdriver)
  30. })
  31. it('Execute action and verify log is saved to disk', async function () {
  32. this.timeout(30000)
  33. await getRootAndWait()
  34. // Get initial log file count
  35. const initialLogCount = fs.existsSync(logsDir)
  36. ? fs.readdirSync(logsDir).filter(f => f.endsWith('.yaml')).length
  37. : 0
  38. // Wait for action button to be available
  39. await webdriver.wait(
  40. new Condition('wait for Echo Test button', async () => {
  41. const buttons = await webdriver.findElements(By.css('.action-button button'))
  42. for (const btn of buttons) {
  43. const text = await btn.getText()
  44. if (text.includes('Echo Test')) {
  45. return true
  46. }
  47. }
  48. return false
  49. }),
  50. 10000
  51. )
  52. // Find and click the Echo Test button
  53. const buttons = await webdriver.findElements(By.css('.action-button button'))
  54. let echoButton = null
  55. for (const btn of buttons) {
  56. const text = await btn.getText()
  57. if (text.includes('Echo Test')) {
  58. echoButton = btn
  59. break
  60. }
  61. }
  62. expect(echoButton).to.not.be.null
  63. // Click the button to execute the action
  64. await echoButton.click()
  65. // Wait for the log file to be written to disk
  66. await webdriver.wait(
  67. new Condition('wait for log file to appear', async () => {
  68. if (!fs.existsSync(logsDir)) {
  69. return false
  70. }
  71. const logFiles = fs.readdirSync(logsDir).filter(f => f.endsWith('.yaml'))
  72. return logFiles.length > initialLogCount
  73. }),
  74. 10000
  75. )
  76. // Wait a bit more to ensure file is fully written
  77. await webdriver.sleep(1000)
  78. // Get the newest log file
  79. const logFiles = fs.readdirSync(logsDir).filter(f => f.endsWith('.yaml'))
  80. expect(logFiles.length).to.be.greaterThan(initialLogCount, 'At least one new log file should be saved')
  81. // Sort by modification time to get the newest
  82. const logFilesWithStats = logFiles.map(f => {
  83. const filePath = path.join(logsDir, f)
  84. return {
  85. name: f,
  86. path: filePath,
  87. mtime: fs.statSync(filePath).mtime
  88. }
  89. }).sort((a, b) => b.mtime - a.mtime)
  90. const newestLogFile = logFilesWithStats[0]
  91. expect(newestLogFile).to.not.be.undefined
  92. // Read the log file to extract the tracking ID
  93. const logFileContent = fs.readFileSync(newestLogFile.path, 'utf8')
  94. // Verify the log file contains expected content (action title might be in different fields)
  95. expect(logFileContent.length).to.be.greaterThan(0, 'Log file should not be empty')
  96. // Extract tracking ID from filename first (most reliable)
  97. // Filename format: <title>.<timestamp>.<trackingId>.yaml
  98. // Tracking IDs are UUIDs, so match UUID pattern at the end before .yaml
  99. let uuidMatch = newestLogFile.name.match(/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})\.yaml$/)
  100. if (uuidMatch) {
  101. firstExecutionTrackingId = uuidMatch[1]
  102. } else {
  103. // Fallback: split by dots and get the last part before .yaml
  104. const parts = newestLogFile.name.replace('.yaml', '').split('.')
  105. if (parts.length >= 3) {
  106. // The last part should be the tracking ID
  107. firstExecutionTrackingId = parts[parts.length - 1]
  108. }
  109. }
  110. // If still not found, try to extract from YAML content
  111. // Try different possible field name variations
  112. if (!firstExecutionTrackingId) {
  113. const patterns = [
  114. /executionTrackingID:\s*([^\s\n]+)/i,
  115. /execution_tracking_id:\s*([^\s\n]+)/i,
  116. /ExecutionTrackingID:\s*([^\s\n]+)/,
  117. /executionTrackingId:\s*([^\s\n]+)/i,
  118. ]
  119. for (const pattern of patterns) {
  120. const match = logFileContent.match(pattern)
  121. if (match) {
  122. firstExecutionTrackingId = match[1].trim()
  123. break
  124. }
  125. }
  126. }
  127. expect(firstExecutionTrackingId).to.not.be.null
  128. expect(firstExecutionTrackingId.length).to.be.greaterThan(0)
  129. // Verify the log file name contains the tracking ID
  130. expect(newestLogFile.name).to.include(firstExecutionTrackingId)
  131. // Verify the log file content contains the action (might be in actionTitle, actionConfigTitle, or title field)
  132. const hasActionReference = logFileContent.includes('Echo Test') ||
  133. logFileContent.includes('echo') ||
  134. logFileContent.includes('actionTitle') ||
  135. logFileContent.includes('actionConfigTitle')
  136. expect(hasActionReference).to.be.true
  137. })
  138. it('Restart service and verify logs are loaded from disk', async function () {
  139. this.timeout(60000)
  140. // Skip if first test didn't set the tracking ID
  141. if (!firstExecutionTrackingId) {
  142. this.skip()
  143. }
  144. // Verify log file exists before restart
  145. const logFilesBeforeRestart = fs.readdirSync(logsDir).filter(f => f.endsWith('.yaml'))
  146. expect(logFilesBeforeRestart.length).to.be.greaterThan(0, 'Log file should exist before restart')
  147. // Find the log file for this execution
  148. const matchingLogFileBefore = logFilesBeforeRestart.find(f => f.includes(firstExecutionTrackingId))
  149. expect(matchingLogFileBefore).to.not.be.undefined
  150. // Stop the current service instance
  151. await runner.stop()
  152. // Wait a moment to ensure the process has fully stopped
  153. await new Promise((resolve) => setTimeout(resolve, 2000))
  154. // Verify log file still exists after stop (should not be deleted)
  155. const logFilesAfterStop = fs.readdirSync(logsDir).filter(f => f.endsWith('.yaml'))
  156. expect(logFilesAfterStop.length).to.be.greaterThan(0, 'Log file should still exist after service stop')
  157. const matchingLogFileAfter = logFilesAfterStop.find(f => f.includes(firstExecutionTrackingId))
  158. expect(matchingLogFileAfter).to.not.be.undefined
  159. // Start a new service instance (logs should be loaded from disk)
  160. await runner.start('logPersistence')
  161. // Wait for the service to fully start and load logs
  162. await new Promise((resolve) => setTimeout(resolve, 3000))
  163. await getRootAndWait()
  164. // Navigate directly to the specific log entry (this verifies the log was loaded)
  165. await webdriver.get(runner.baseUrl() + 'logs/' + firstExecutionTrackingId)
  166. // Wait for the log details page to load
  167. await webdriver.wait(
  168. new Condition('wait for log details to load', async () => {
  169. try {
  170. const body = await webdriver.findElement(By.tagName('body'))
  171. const text = await body.getText()
  172. // The log should contain the output from the echo command
  173. return text.includes('Hello from persisted log test') || text.includes(firstExecutionTrackingId)
  174. } catch {
  175. return false
  176. }
  177. }),
  178. 15000
  179. )
  180. // Verify the log content is displayed
  181. const body = await webdriver.findElement(By.tagName('body'))
  182. const bodyText = await body.getText()
  183. // The persisted log should be accessible and contain the expected output
  184. expect(bodyText).to.include('Hello from persisted log test')
  185. })
  186. it('Verify log file still exists after restart', async function () {
  187. // Skip if first test didn't set the tracking ID
  188. if (!firstExecutionTrackingId) {
  189. this.skip()
  190. }
  191. // Verify the log file still exists on disk
  192. const logFiles = fs.readdirSync(logsDir).filter(f => f.endsWith('.yaml'))
  193. expect(logFiles.length).to.be.greaterThan(0, 'Log files should still exist after restart')
  194. // Find the log file for the first execution
  195. const matchingLogFile = logFiles.find(f => f.includes(firstExecutionTrackingId))
  196. expect(matchingLogFile).to.not.be.undefined
  197. expect(matchingLogFile).to.not.be.null
  198. // Verify the log file content is still valid
  199. const logFilePath = path.join(logsDir, matchingLogFile)
  200. const logFileContent = fs.readFileSync(logFilePath, 'utf8')
  201. expect(logFileContent.length).to.be.greaterThan(0, 'Log file should not be empty')
  202. // The filename contains the tracking ID, so verify that
  203. expect(matchingLogFile).to.include(firstExecutionTrackingId)
  204. // Verify the file contains some expected content (action reference)
  205. const hasActionReference = logFileContent.includes('Echo Test') ||
  206. logFileContent.includes('echo') ||
  207. logFileContent.includes('actionTitle') ||
  208. logFileContent.includes('actionConfigTitle')
  209. expect(hasActionReference).to.be.true
  210. })
  211. })