logPersistence.mjs 9.2 KB

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