summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/transcription-config.el84
-rw-r--r--tests/test-transcription-sentinel-helpers.el136
2 files changed, 180 insertions, 40 deletions
diff --git a/modules/transcription-config.el b/modules/transcription-config.el
index e8fe1159..1c864b95 100644
--- a/modules/transcription-config.el
+++ b/modules/transcription-config.el
@@ -193,53 +193,57 @@ Returns the process object."
(format "Started on %s" (file-name-nondirectory audio-file)))
process)))
-(defun cj/--transcription-sentinel (process event audio-file txt-file log-file)
+(defun cj/--write-transcript-on-success (process-buffer success-p txt-file)
+ "Write PROCESS-BUFFER contents to TXT-FILE when SUCCESS-P is non-nil.
+No-op if PROCESS-BUFFER is dead or SUCCESS-P is nil."
+ (when (and success-p (buffer-live-p process-buffer))
+ (with-current-buffer process-buffer
+ (write-region (point-min) (point-max) txt-file nil 'silent))))
+
+(defun cj/--append-to-log (process-buffer log-file event)
+ "Append an EVENT marker plus PROCESS-BUFFER contents to LOG-FILE.
+No-op if PROCESS-BUFFER is dead."
+ (when (buffer-live-p process-buffer)
+ (with-temp-buffer
+ (insert-file-contents log-file)
+ (goto-char (point-max))
+ (insert "\n" (format-time-string "[%Y-%m-%d %H:%M:%S] ") event "\n")
+ (insert-buffer-substring process-buffer)
+ (write-region (point-min) (point-max) log-file nil 'silent))))
+
+(defun cj/--update-transcription-status (process success-p)
+ "Mark PROCESS's entry as `complete' or `error' based on SUCCESS-P.
+No-op if PROCESS isn't tracked."
+ (when-let ((entry (assq process cj/transcriptions-list)))
+ (setf (nth 3 entry) (if success-p 'complete 'error))))
+
+(defun cj/--notify-completion (success-p txt-file log-file)
+ "Send completion notification based on SUCCESS-P.
+References TXT-FILE on success (normal urgency), LOG-FILE on failure
+\(critical urgency)."
+ (if success-p
+ (cj/--notify "Transcription"
+ (format "Complete. Transcript in %s" (file-name-nondirectory txt-file)))
+ (cj/--notify "Transcription"
+ (format "Errored. Logs in %s" (file-name-nondirectory log-file))
+ 'critical)))
+
+(defun cj/--transcription-sentinel (process event _audio-file txt-file log-file)
"Sentinel for transcription PROCESS.
-EVENT is the process event string.
-AUDIO-FILE, TXT-FILE, and LOG-FILE are the associated files."
+EVENT is the process event string. TXT-FILE and LOG-FILE are the
+associated output files."
(let* ((success-p (and (string-match-p "finished" event)
(= 0 (process-exit-status process))))
- (process-buffer (process-buffer process))
- (entry (assq process cj/transcriptions-list)))
-
- ;; Write process output to txt file
- (when (and success-p (buffer-live-p process-buffer))
- (with-current-buffer process-buffer
- (write-region (point-min) (point-max) txt-file nil 'silent)))
-
- ;; Append process output to log file
- (when (buffer-live-p process-buffer)
- (with-temp-buffer
- (insert-file-contents log-file)
- (goto-char (point-max))
- (insert "\n" (format-time-string "[%Y-%m-%d %H:%M:%S] ") event "\n")
- (insert-buffer-substring process-buffer)
- (write-region (point-min) (point-max) log-file nil 'silent)))
-
- ;; Update transcription status
- (when entry
- (setf (nth 3 entry) (if success-p 'complete 'error)))
-
- ;; Cleanup log file if successful and configured to do so
- (when (and success-p (not (cj/--should-keep-log t)))
+ (process-buffer (process-buffer process)))
+ (cj/--write-transcript-on-success process-buffer success-p txt-file)
+ (cj/--append-to-log process-buffer log-file event)
+ (cj/--update-transcription-status process success-p)
+ (when (and success-p (not (cj/--should-keep-log success-p)))
(delete-file log-file))
-
- ;; Kill process buffer
(when (buffer-live-p process-buffer)
(kill-buffer process-buffer))
-
- ;; Notify user
- (if success-p
- (cj/--notify "Transcription"
- (format "Complete. Transcript in %s" (file-name-nondirectory txt-file)))
- (cj/--notify "Transcription"
- (format "Errored. Logs in %s" (file-name-nondirectory log-file))
- 'critical))
-
- ;; Clean up completed transcriptions after 10 minutes
+ (cj/--notify-completion success-p txt-file log-file)
(run-at-time 600 nil #'cj/--cleanup-completed-transcriptions)
-
- ;; Update modeline
(force-mode-line-update t)))
(defun cj/--cleanup-completed-transcriptions ()
diff --git a/tests/test-transcription-sentinel-helpers.el b/tests/test-transcription-sentinel-helpers.el
new file mode 100644
index 00000000..b743acf9
--- /dev/null
+++ b/tests/test-transcription-sentinel-helpers.el
@@ -0,0 +1,136 @@
+;;; test-transcription-sentinel-helpers.el --- Tests for sentinel helpers -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the four helpers extracted from `cj/--transcription-sentinel':
+;; - `cj/--write-transcript-on-success' (writes process output to txt)
+;; - `cj/--append-to-log' (appends event + output to log)
+;; - `cj/--update-transcription-status' (mutates tracking-list status)
+;; - `cj/--notify-completion' (sends completion notification)
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+
+(defvar cj/custom-keymap (make-sparse-keymap))
+
+(unless (fboundp 'notifications-notify)
+ (defun notifications-notify (&rest _args) nil))
+
+(require 'transcription-config)
+
+(defmacro test-sentinel-with-temp-file (var extension &rest body)
+ "Bind VAR to a fresh temp file path with EXTENSION and run BODY."
+ (declare (indent 2))
+ `(let ((,var (make-temp-file "transcription-sentinel-" nil ,extension)))
+ (unwind-protect
+ (progn ,@body)
+ (when (file-exists-p ,var) (delete-file ,var)))))
+
+(defmacro test-sentinel-with-process-buffer (var content &rest body)
+ "Bind VAR to a fresh buffer containing CONTENT; kill it after BODY."
+ (declare (indent 2))
+ `(let ((,var (generate-new-buffer " *test-sentinel-proc*")))
+ (unwind-protect
+ (progn
+ (with-current-buffer ,var (insert ,content))
+ ,@body)
+ (when (buffer-live-p ,var) (kill-buffer ,var)))))
+
+(defun test-sentinel-file-contents (path)
+ (with-temp-buffer (insert-file-contents path) (buffer-string)))
+
+;;; cj/--write-transcript-on-success
+
+(ert-deftest test-sentinel-write-transcript-normal-writes-on-success ()
+ "On success with a live buffer, writes buffer contents to TXT-FILE."
+ (test-sentinel-with-temp-file txt-file ".txt"
+ (test-sentinel-with-process-buffer buf "hello transcript"
+ (cj/--write-transcript-on-success buf t txt-file)
+ (should (equal "hello transcript" (test-sentinel-file-contents txt-file))))))
+
+(ert-deftest test-sentinel-write-transcript-boundary-noop-on-failure ()
+ "On failure, TXT-FILE is not touched."
+ (test-sentinel-with-temp-file txt-file ".txt"
+ (delete-file txt-file)
+ (test-sentinel-with-process-buffer buf "ignore me"
+ (cj/--write-transcript-on-success buf nil txt-file))
+ (should-not (file-exists-p txt-file))))
+
+(ert-deftest test-sentinel-write-transcript-boundary-noop-on-dead-buffer ()
+ "A dead process buffer is a no-op, even on success."
+ (test-sentinel-with-temp-file txt-file ".txt"
+ (delete-file txt-file)
+ (let ((buf (generate-new-buffer " *dead*")))
+ (kill-buffer buf)
+ (cj/--write-transcript-on-success buf t txt-file))
+ (should-not (file-exists-p txt-file))))
+
+;;; cj/--append-to-log
+
+(ert-deftest test-sentinel-append-to-log-normal-appends-event-and-output ()
+ "Appends a timestamped event line and the process-buffer contents."
+ (test-sentinel-with-temp-file log-file ".log"
+ (with-temp-file log-file (insert "HEADER\n"))
+ (test-sentinel-with-process-buffer buf "process stderr here"
+ (cj/--append-to-log buf log-file "finished\n"))
+ (let ((contents (test-sentinel-file-contents log-file)))
+ (should (string-match-p "HEADER" contents))
+ (should (string-match-p "finished" contents))
+ (should (string-match-p "process stderr here" contents)))))
+
+(ert-deftest test-sentinel-append-to-log-boundary-noop-on-dead-buffer ()
+ "A dead process buffer is a no-op."
+ (test-sentinel-with-temp-file log-file ".log"
+ (with-temp-file log-file (insert "ORIGINAL\n"))
+ (let ((buf (generate-new-buffer " *dead*")))
+ (kill-buffer buf)
+ (cj/--append-to-log buf log-file "event"))
+ (should (equal "ORIGINAL\n" (test-sentinel-file-contents log-file)))))
+
+;;; cj/--update-transcription-status
+
+(ert-deftest test-sentinel-update-status-normal-success-marks-complete ()
+ "On success, the matching entry's status becomes `complete'."
+ (let ((cj/transcriptions-list '((proc-a "/a.m4a" nil running)
+ (proc-b "/b.m4a" nil running))))
+ (cj/--update-transcription-status 'proc-a t)
+ (should (eq 'complete (nth 3 (assq 'proc-a cj/transcriptions-list))))
+ (should (eq 'running (nth 3 (assq 'proc-b cj/transcriptions-list))))))
+
+(ert-deftest test-sentinel-update-status-normal-failure-marks-error ()
+ "On failure, the matching entry's status becomes `error'."
+ (let ((cj/transcriptions-list '((proc-a "/a.m4a" nil running))))
+ (cj/--update-transcription-status 'proc-a nil)
+ (should (eq 'error (nth 3 (assq 'proc-a cj/transcriptions-list))))))
+
+(ert-deftest test-sentinel-update-status-boundary-unknown-process-noop ()
+ "Updating a process that isn't in the list is a no-op."
+ (let ((cj/transcriptions-list '((proc-a "/a.m4a" nil running))))
+ (cj/--update-transcription-status 'proc-unknown t)
+ (should (eq 'running (nth 3 (assq 'proc-a cj/transcriptions-list))))))
+
+;;; cj/--notify-completion
+
+(ert-deftest test-sentinel-notify-completion-normal-success-mentions-txt ()
+ "On success, notification body references the TXT-FILE."
+ (let (captured)
+ (cl-letf (((symbol-function 'cj/--notify)
+ (lambda (title body &optional urgency)
+ (setq captured (list title body urgency)))))
+ (cj/--notify-completion t "/tmp/out.txt" "/tmp/out.log"))
+ (should (string-match-p "out\\.txt" (nth 1 captured)))
+ (should-not (nth 2 captured)))) ; normal urgency = nil
+
+(ert-deftest test-sentinel-notify-completion-normal-failure-mentions-log-and-critical ()
+ "On failure, notification body references the LOG-FILE at critical urgency."
+ (let (captured)
+ (cl-letf (((symbol-function 'cj/--notify)
+ (lambda (title body &optional urgency)
+ (setq captured (list title body urgency)))))
+ (cj/--notify-completion nil "/tmp/out.txt" "/tmp/out.log"))
+ (should (string-match-p "out\\.log" (nth 1 captured)))
+ (should (eq 'critical (nth 2 captured)))))
+
+(provide 'test-transcription-sentinel-helpers)
+;;; test-transcription-sentinel-helpers.el ends here