summaryrefslogtreecommitdiff
path: root/tests/test-video-audio-recording--get-sink-apps.el
blob: 8712fa9713bbae4173180ddca265c0ced1fbf1e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
;;; test-video-audio-recording--get-sink-apps.el --- Tests for sink app discovery -*- lexical-binding: t; -*-

;;; Commentary:
;; Unit tests for cj/recording--get-sink-apps.
;; Verifies parsing of `pactl list sink-inputs' output to map
;; sink indices to application names.

;;; Code:

(require 'ert)

;; Stub dependencies before loading the module
(defvar cj/custom-keymap (make-sparse-keymap)
  "Stub keymap for testing.")

(require 'video-audio-recording)

;;; Helpers

(defvar test-sink-apps--dir
  (file-name-directory (or load-file-name (locate-library "test-video-audio-recording--get-sink-apps")))
  "Directory containing this test file.")

(defun test-sink-apps--fixture (filename)
  "Read fixture FILENAME from the fixtures directory."
  (with-temp-buffer
    (insert-file-contents (expand-file-name (concat "fixtures/" filename) test-sink-apps--dir))
    (buffer-string)))

;;; Normal Cases

(ert-deftest test-video-audio-recording--get-sink-apps-normal-single-app ()
  "Test parsing a single app on a single sink."
  (cl-letf (((symbol-function 'shell-command-to-string)
             (lambda (_cmd) (test-sink-apps--fixture "pactl-sink-inputs-active.txt"))))
    (let ((result (cj/recording--get-sink-apps)))
      (should (= 1 (length result)))
      (should (equal '("Firefox") (cdr (assoc "65" result)))))))

(ert-deftest test-video-audio-recording--get-sink-apps-normal-different-sink ()
  "Test that sink index is correctly parsed from different fixture."
  (cl-letf (((symbol-function 'shell-command-to-string)
             (lambda (_cmd) (test-sink-apps--fixture "pactl-sink-inputs-different-sink.txt"))))
    (let ((result (cj/recording--get-sink-apps)))
      (should (= 1 (length result)))
      (should (assoc "73" result))
      (should-not (assoc "65" result)))))

(ert-deftest test-video-audio-recording--get-sink-apps-normal-multiple-sinks ()
  "Test parsing apps across multiple sinks."
  (cl-letf (((symbol-function 'shell-command-to-string)
             (lambda (_cmd)
               (concat (test-sink-apps--fixture "pactl-sink-inputs-active.txt")
                       "\n"
                       (test-sink-apps--fixture "pactl-sink-inputs-different-sink.txt")))))
    (let ((result (cj/recording--get-sink-apps)))
      (should (= 2 (length result)))
      (should (assoc "65" result))
      (should (assoc "73" result)))))

(ert-deftest test-video-audio-recording--get-sink-apps-normal-multiple-apps-same-sink ()
  "Test that multiple apps on the same sink are collected together."
  (let ((output "Sink Input #1\n\tSink: 65\n\tProperties:\n\t\tapplication.name = \"Firefox\"\nSink Input #2\n\tSink: 65\n\tProperties:\n\t\tapplication.name = \"Spotify\"\n"))
    (cl-letf (((symbol-function 'shell-command-to-string)
               (lambda (_cmd) output)))
      (let ((result (cj/recording--get-sink-apps)))
        (should (= 1 (length result)))
        (should (equal '("Firefox" "Spotify") (cdr (assoc "65" result))))))))

(ert-deftest test-video-audio-recording--get-sink-apps-normal-deduplicates ()
  "Test that duplicate app names on the same sink are deduplicated."
  (let ((output "Sink Input #1\n\tSink: 65\n\tProperties:\n\t\tapplication.name = \"Firefox\"\nSink Input #2\n\tSink: 65\n\tProperties:\n\t\tapplication.name = \"Firefox\"\n"))
    (cl-letf (((symbol-function 'shell-command-to-string)
               (lambda (_cmd) output)))
      (let ((result (cj/recording--get-sink-apps)))
        (should (equal '("Firefox") (cdr (assoc "65" result))))))))

;;; Boundary Cases

(ert-deftest test-video-audio-recording--get-sink-apps-boundary-empty ()
  "Test that empty pactl output returns empty alist."
  (cl-letf (((symbol-function 'shell-command-to-string)
             (lambda (_cmd) "")))
    (should (null (cj/recording--get-sink-apps)))))

(ert-deftest test-video-audio-recording--get-sink-apps-boundary-no-properties ()
  "Test sink input with no properties section returns empty."
  (let ((output "Sink Input #1\n\tSink: 65\n\tDriver: PipeWire\n"))
    (cl-letf (((symbol-function 'shell-command-to-string)
               (lambda (_cmd) output)))
      (should (null (cj/recording--get-sink-apps))))))

(ert-deftest test-video-audio-recording--get-sink-apps-boundary-no-sink-line ()
  "Test sink input with no Sink: line does not crash."
  (let ((output "Sink Input #1\n\tDriver: PipeWire\n\tProperties:\n\t\tapplication.name = \"Firefox\"\n"))
    (cl-letf (((symbol-function 'shell-command-to-string)
               (lambda (_cmd) output)))
      ;; current-sink is nil so app name won't be stored
      (should (null (cj/recording--get-sink-apps))))))

;;; Error Cases

(ert-deftest test-video-audio-recording--get-sink-apps-error-garbled-output ()
  "Test that garbled output does not crash, returns empty."
  (cl-letf (((symbol-function 'shell-command-to-string)
             (lambda (_cmd) "random garbage\nwith\nnewlines\n")))
    (should (null (cj/recording--get-sink-apps)))))

(ert-deftest test-video-audio-recording--get-sink-apps-error-missing-app-name ()
  "Test sink input with application.name missing value."
  (let ((output "Sink Input #1\n\tSink: 65\n\tProperties:\n\t\tapplication.name = \"\"\n"))
    (cl-letf (((symbol-function 'shell-command-to-string)
               (lambda (_cmd) output)))
      ;; Empty string between quotes won't match [^\"]+ so no app stored
      (should (null (cj/recording--get-sink-apps))))))

(provide 'test-video-audio-recording--get-sink-apps)
;;; test-video-audio-recording--get-sink-apps.el ends here