expect-pty 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +20 -3
- data/docs/INTERNAL_CONTRACTS.md +8 -3
- data/docs/VERIFICATION.md +8 -0
- data/lib/expect/redactor.rb +40 -15
- data/lib/expect/version.rb +1 -1
- data/script/ci +5 -1
- data/test/redactor_test.rb +106 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5f3f00d375bb8a90ef67974bbd7649b031554e15f9d463cf28f1388d5d3e5dc1
|
|
4
|
+
data.tar.gz: 9c9a556719b8c79c43553bc344fa679737abb2330f8ecdec72beabfedb3842d7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 53fa1a25e62c9c16c2ecbafc3efa38ba4acd2c805afa6b1547e4b83cb95c98484bbd27f612a83397a9eb9a0715af857b7f9f4d698f52bdcaa4b7dcbd3a55a42e
|
|
7
|
+
data.tar.gz: 226b72621b685c9fce34097838e0c10a81f616b3ba26a62a90eb6f09c9e3a6e0a3cd5cac331ba79c9a81caf134707821622a3e1ebe85842aff01da79b1d1d554
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.5.0 - 2026-09-27
|
|
6
|
+
|
|
7
|
+
- **独立脱敏**:新增无需创建会话的 `Expect::Redactor`,支持完整文本和分块字节流过滤,以及自定义替换标记。
|
|
8
|
+
- **过滤规则**:支持空模式列表和复制校验后的规则更新,完整文本仅匹配完整秘密,流结束时默认隐藏未完成的秘密前缀,现有会话脱敏行为保持不变。
|
|
9
|
+
|
|
5
10
|
## 0.4.0 - 2026-09-27
|
|
6
11
|
|
|
7
12
|
- **缓冲与生命周期**:新增只读 `buffer_discarded_bytes`,区分上限裁剪与正常消费;补齐外部回收进程、借用 IO、重复关闭和失败重试的契约回归。
|
data/README.md
CHANGED
|
@@ -16,7 +16,7 @@ RubyGems/Bundler 解析。推荐入口 **`require "expect/pty"`**;本项目提
|
|
|
16
16
|
项目和仓库名为 `expect-ruby`,Gem 名为 `expect-pty`。在应用的 Gemfile 中添加以下内容,然后运行 `bundle install`:
|
|
17
17
|
|
|
18
18
|
```ruby
|
|
19
|
-
gem "expect-pty", "~> 0.
|
|
19
|
+
gem "expect-pty", "~> 0.5.0", require: "expect/pty"
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
也可直接执行 `gem install expect-pty`。需要跟随开发分支时,可从 GitHub 安装:
|
|
@@ -29,8 +29,8 @@ gem "expect-pty", git: "https://github.com/gatework/expect-ruby.git", branch: "m
|
|
|
29
29
|
|
|
30
30
|
```sh
|
|
31
31
|
mkdir -p tmp
|
|
32
|
-
gem build expect-pty.gemspec --output tmp/expect-pty-0.
|
|
33
|
-
gem install ./tmp/expect-pty-0.
|
|
32
|
+
gem build expect-pty.gemspec --output tmp/expect-pty-0.5.0.gem
|
|
33
|
+
gem install ./tmp/expect-pty-0.5.0.gem
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
```ruby
|
|
@@ -290,6 +290,23 @@ session.log_to("session.log")
|
|
|
290
290
|
3 的缓冲快照只显示 `[FILTERED]`,避免部分消费或裁剪后剩下的秘密片段绕过过滤。日志回调的分块边界因此可能变化。GC
|
|
291
291
|
兜底不会调用用户日志回调,需显式关闭会话以交付过滤器尾部。
|
|
292
292
|
|
|
293
|
+
应用需要过滤自己的日志或错误文本时,可以直接使用独立的字节过滤器,无需打开 PTY 或创建会话:
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
require "expect/redactor"
|
|
297
|
+
|
|
298
|
+
safe_message = Expect::Redactor.redact(message, [password], replacement: "[REDACTED]")
|
|
299
|
+
filter = Expect::Redactor.new([password], replacement: "[REDACTED]")
|
|
300
|
+
output.write(filter.append(chunk)) # 每个输出流使用独立实例
|
|
301
|
+
output.write(filter.finish)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
`patterns=` 用由非空字符串组成的数组替换后续规则,空数组表示不注册秘密;输入数组、字符串和替换标记都会复制,
|
|
305
|
+
无效更新不改变已有规则或暂存数据。已输出的内容不能撤回,更新后也会保留先前标记为隐藏的尾部区间。
|
|
306
|
+
`redact` 处理完整文本,只匹配完整秘密;`finish` 默认还隐藏未完成的秘密前缀。已确定输入完整的调用方可用
|
|
307
|
+
`finish(partial: false)`。连续或重叠的隐藏区间合并成一个替换标记。过滤不自动识别终端控制符或编码,过滤器本身不持有 IO,
|
|
308
|
+
也不负责会话作用域或异常对象的安全字段选择。该公共接口从 0.5.0 开始提供。
|
|
309
|
+
|
|
293
310
|
```ruby
|
|
294
311
|
session.interact(input: $stdin, escape: "\x1d", output: $stdout) # Ctrl-]
|
|
295
312
|
|
data/docs/INTERNAL_CONTRACTS.md
CHANGED
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
| `terminal.rb` | `stty` 和窗口尺寸的会话终端接口 |
|
|
16
16
|
| `interaction.rb`、`relay.rb`、`relay_writer.rb` | 人工接管、转义处理、共同调度和各目标发送游标 |
|
|
17
17
|
|
|
18
|
-
`redactor.rb`
|
|
18
|
+
`redactor.rb` 提供公共 `Expect::Redactor` 字节过滤接口,供会话日志、各方向诊断及上层库共用,不持有 IO,也不接管协议缓冲。
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
会话能力文件延续 `interaction.rb` 的类重开方式,入口为 `expect/pty`;独立过滤器可通过 `expect/redactor` 加载。按职责组织方法,不引入新的继承层级,也不复制会话状态。
|
|
21
21
|
|
|
22
22
|
跨对象调用使用 `__send__` 访问受保护或私有方法。变更以下入口时,应同时检查调用方与失败恢复路径:
|
|
23
23
|
|
|
@@ -77,7 +77,11 @@ exec 成功后诊断输出失败,也要立即回收未交付给调用方的子
|
|
|
77
77
|
|
|
78
78
|
`logging.rb` 分开处理接收日志、诊断和协议转发。`diagnostic_output` 借用 Logger、可写对象或回调,不进入资源账本;默认沿用 stderr。回调接收冻结的事件 Hash 和 message 字符串,不含会话对象。诊断失败与其他同步 IO 错误同样保留已读取的原始输入。
|
|
79
79
|
|
|
80
|
-
`redactor.rb`
|
|
80
|
+
`redactor.rb` 是无 IO 的公共字节过滤器。会话复制并追加注册秘密,每个接收日志流以及发送、接收诊断方向各自持有过滤器。暂存最长秘密长度减一的尾部,重叠秘密合并为隐藏区间,过滤发生在 `inspect` 转义之前。缓冲快照可能只剩秘密中间字节,启用脱敏时整体隐藏,不重复展示其内容。
|
|
81
|
+
|
|
82
|
+
公共构造器及 `patterns=` 会校验并复制秘密;空模式列表直接交付字节,更新规则不清除已经建立的掩码。
|
|
83
|
+
默认替换标记为 `[FILTERED]`,上层库可显式指定自己的标记。类方法 `redact` 使用独立流并以 `finish(partial: false)` 结束完整文本;
|
|
84
|
+
`append`/`finish` 的默认流策略仍隐藏末尾疑似秘密前缀。作用域所有权、终端渲染和异常字段选择由调用方负责。
|
|
81
85
|
|
|
82
86
|
EOF 结束接收流,日志或诊断目标替换先冲刷旧流,显式关闭结束所有方向;尾部疑似秘密前缀保守隐藏。GC 只清理所属资源,不执行用户回调或过滤尾部输出。调用方需显式关闭以交付尾部;借用日志和诊断目标不关闭,库打开的日志文件仍由 SessionResources 管理。
|
|
83
87
|
|
|
@@ -99,6 +103,7 @@ EOF 结束接收流,日志或诊断目标替换先冲刷旧流,显式关闭
|
|
|
99
103
|
| 外部回收、借用 IO、关闭失败重试、原生错误分类 | lifecycle_contract_test |
|
|
100
104
|
| 绝对期限、接收和回调重置、过期文本、EOF、EINTR、零轮询 | deadline_test |
|
|
101
105
|
| Logger/IO/回调、分片与重叠秘密、二进制、流尾部、关闭失败、覆盖与重入 | diagnostics_test |
|
|
106
|
+
| 公共过滤器独立加载、完整文本与分块、空规则、规则更新、输入复制和安全摘要 | redactor_test |
|
|
102
107
|
| 字面跨读取命中、声明优先级、缓存失效、操作序列与全量扫描对照 | literal_scan_test |
|
|
103
108
|
|
|
104
109
|
## 既有回归入口
|
data/docs/VERIFICATION.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# 验证记录
|
|
2
2
|
|
|
3
|
+
## 0.5.0 公共字节过滤器发布前复核(2026-09-27)
|
|
4
|
+
|
|
5
|
+
基线为已发布的 `5a219f7`(0.4.0)。本轮公开 `Expect::Redactor`,新增 11 项回归,覆盖独立加载不引入 PTY、完整文本与流尾部策略、跨分片与重叠秘密、自定义替换标记、空规则、输入复制、无效更新原子性及安全摘要;现有会话日志与诊断继续复用该过滤器。
|
|
6
|
+
|
|
7
|
+
从基线建立独立检出,仅带入本轮改动。macOS arm64、Ruby 4.0.6、Bundler 4.0.17 下执行 `ruby script/release.rb --dry-run`,330 项测试、1,894 条断言全部通过,无失败、错误或跳过;RuboCop 检查 59 个文件无违规。四组基准 smoke、Gem 构建、普通 RubyGems 与最小 Bundler 应用的隔离安装均通过,安装后实际调用公共完整文本和分块过滤接口。
|
|
8
|
+
|
|
9
|
+
此处记录本地发布预演;远端 Linux/macOS 与 Ruby 3.2/3.3/3.4/4.0 矩阵、标签及下载包校验,以 0.5.0 发布提交对应的 GitHub Actions 与 Release 为准。
|
|
10
|
+
|
|
3
11
|
## 0.4.0 发布前复核(2026-09-27)
|
|
4
12
|
|
|
5
13
|
发布前新增七项回归:总期限在文本回调后到达时,已知 EOF 先于剩余活跃来源的超时派发;覆盖日志路径前交付旧尾部,冲刷失败不能提前截断新文件;嵌套诊断等待保留外层匹配结果;日志回调轮换目标时正确交接所有权;诊断回调新建或再次写入另一方向时,尾部仍交给旧目标。各项均先复现失败,再验证修复,纯相对超时行为保持不变。
|
data/lib/expect/redactor.rb
CHANGED
|
@@ -3,10 +3,21 @@
|
|
|
3
3
|
class Expect
|
|
4
4
|
# 单个日志字节流的过滤器。保留最长秘密长度减一的尾部,跨 write/read 分片仍可识别。
|
|
5
5
|
# 掩码与原字节一起留存;重叠命中的区间取并集,已经输出的掩码不重复生成。
|
|
6
|
-
#
|
|
6
|
+
# 不依赖会话或 IO;每个日志目标或诊断方向使用独立实例。
|
|
7
7
|
class Redactor
|
|
8
|
+
# 完整诊断文本只匹配完整秘密;流边界的疑似秘密前缀由 finish 的默认策略保护。
|
|
9
|
+
def self.redact(data, patterns, replacement: "[FILTERED]")
|
|
10
|
+
filter = new(patterns, replacement: replacement)
|
|
11
|
+
filter.append(data) + filter.finish(partial: false)
|
|
12
|
+
end
|
|
13
|
+
|
|
8
14
|
# pending 保存尚不能安全输出的原字节,hidden 的对应字节用 0/1 表示是否需要遮盖。
|
|
9
|
-
def initialize(patterns)
|
|
15
|
+
def initialize(patterns, replacement: "[FILTERED]")
|
|
16
|
+
unless replacement.is_a?(String) && !replacement.empty?
|
|
17
|
+
raise ArgumentError, "replacement must be a nonempty String"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
@replacement = replacement.b.freeze
|
|
10
21
|
self.patterns = patterns
|
|
11
22
|
@pending = "".b
|
|
12
23
|
@hidden = "".b
|
|
@@ -15,21 +26,42 @@ class Expect
|
|
|
15
26
|
|
|
16
27
|
# 更新后续匹配规则并保留已有尾部与掩码;不能追溯修改已经交付给日志目标的内容。
|
|
17
28
|
def patterns=(patterns)
|
|
18
|
-
|
|
19
|
-
|
|
29
|
+
unless patterns.is_a?(Array) && patterns.all? { |pattern| pattern.is_a?(String) && !pattern.empty? }
|
|
30
|
+
raise ArgumentError, "patterns must be an Array of nonempty Strings"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
@patterns = patterns.map { |pattern| pattern.b.freeze }.uniq.freeze
|
|
34
|
+
@lookbehind = [(@patterns.map(&:bytesize).max || 0) - 1, 0].max
|
|
20
35
|
end
|
|
21
36
|
|
|
22
37
|
# 追加一个原始字节块,返回已经可以确定的安全前缀;新秘密可能跨越此前保留的尾部。
|
|
23
38
|
def append(data)
|
|
24
|
-
|
|
39
|
+
raise ArgumentError, "data must be a String" unless data.is_a?(String)
|
|
40
|
+
|
|
41
|
+
@pending << data.b
|
|
25
42
|
@hidden << ("\0" * data.bytesize)
|
|
26
43
|
mark_secrets
|
|
27
44
|
release([@pending.bytesize - @lookbehind, 0].max)
|
|
28
45
|
end
|
|
29
46
|
|
|
30
47
|
# EOF、日志目标替换及关闭是流边界;尾部疑似秘密前缀也遮盖,不能因 flush 泄露片段。
|
|
31
|
-
def finish
|
|
48
|
+
def finish(partial: true)
|
|
49
|
+
raise ArgumentError, "partial must be true or false" unless [true, false].include?(partial)
|
|
50
|
+
|
|
32
51
|
mark_secrets
|
|
52
|
+
mark_partial_secrets if partial
|
|
53
|
+
output = release(@pending.bytesize)
|
|
54
|
+
@masking = false
|
|
55
|
+
output
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# 过滤器公开后仍不在诊断摘要中展开注册秘密或尚未交付的原始字节。
|
|
59
|
+
def inspect = "#<#{self.class}>"
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# 流关闭时无法再等待后续字节,默认隐藏与秘密开头一致的未完成尾部。
|
|
64
|
+
def mark_partial_secrets
|
|
33
65
|
@patterns.each do |pattern|
|
|
34
66
|
[pattern.bytesize - 1, @pending.bytesize].min.downto(1) do |length|
|
|
35
67
|
next unless @pending.end_with?(pattern.byteslice(0, length))
|
|
@@ -38,13 +70,8 @@ class Expect
|
|
|
38
70
|
break
|
|
39
71
|
end
|
|
40
72
|
end
|
|
41
|
-
output = release(@pending.bytesize)
|
|
42
|
-
@masking = false
|
|
43
|
-
output
|
|
44
73
|
end
|
|
45
74
|
|
|
46
|
-
private
|
|
47
|
-
|
|
48
75
|
# 每次只将命中区域标为隐藏,不清除旧掩码;偏移逐字节推进以识别相互重叠的秘密。
|
|
49
76
|
def mark_secrets
|
|
50
77
|
@patterns.each do |pattern|
|
|
@@ -56,7 +83,7 @@ class Expect
|
|
|
56
83
|
end
|
|
57
84
|
|
|
58
85
|
# 按连续区间输出,避免逐字节构造字符串;只保存尚可能与下一块组成秘密的后缀。
|
|
59
|
-
# masking 跨 append
|
|
86
|
+
# masking 跨 append 保留,使被分成多个块的同一隐藏区间只输出一次替换标记。
|
|
60
87
|
def release(length)
|
|
61
88
|
output = "".b
|
|
62
89
|
cursor = 0
|
|
@@ -64,7 +91,7 @@ class Expect
|
|
|
64
91
|
hidden = @hidden.getbyte(cursor) == 1
|
|
65
92
|
ending = [@hidden.index(hidden ? "\0" : "\1", cursor) || length, length].min
|
|
66
93
|
if hidden
|
|
67
|
-
output <<
|
|
94
|
+
output << @replacement unless @masking
|
|
68
95
|
else
|
|
69
96
|
output << @pending.byteslice(cursor, ending - cursor)
|
|
70
97
|
end
|
|
@@ -76,6 +103,4 @@ class Expect
|
|
|
76
103
|
output
|
|
77
104
|
end
|
|
78
105
|
end
|
|
79
|
-
|
|
80
|
-
private_constant :Redactor
|
|
81
106
|
end
|
data/lib/expect/version.rb
CHANGED
data/script/ci
CHANGED
|
@@ -68,7 +68,11 @@ Expect.spawn(RbConfig.ruby, "--disable-gems", "-e", child, raw_pty: true, log_st
|
|
|
68
68
|
abort "installed gem child did not exit successfully" unless status&.success?
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
-
#
|
|
71
|
+
# 使用已安装包验证公共过滤器随包分发,且结构化诊断无需加载可选的 Logger。
|
|
72
|
+
filter = Expect::Redactor.new(["secret"], replacement: "[REDACTED]")
|
|
73
|
+
filtered = filter.append("sec") + filter.append("ret!") + filter.finish
|
|
74
|
+
abort "installed public redactor failed" unless filtered == "[REDACTED]!"
|
|
75
|
+
abort "installed complete-text redactor failed" unless Expect::Redactor.redact("sec", ["secret"]) == "sec"
|
|
72
76
|
reader, producer = IO.pipe
|
|
73
77
|
events = []
|
|
74
78
|
transcript = StringIO.new
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "rbconfig"
|
|
6
|
+
require_relative "../lib/expect/redactor"
|
|
7
|
+
|
|
8
|
+
class RedactorTest < Minitest::Test
|
|
9
|
+
def test_standalone_require_does_not_load_pty_or_allocate_a_session
|
|
10
|
+
program = <<~RUBY
|
|
11
|
+
require "expect/redactor"
|
|
12
|
+
abort "PTY loaded" if defined?(PTY)
|
|
13
|
+
abort "unexpected result" unless Expect::Redactor.redact("secret!", ["secret"]) == "[FILTERED]!"
|
|
14
|
+
RUBY
|
|
15
|
+
output, status = Open3.capture2e(RbConfig.ruby, "--disable-gems", "-I", File.expand_path("../lib", __dir__),
|
|
16
|
+
"-e", program)
|
|
17
|
+
assert status.success?, output
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_empty_patterns_pass_binary_input_without_buffering
|
|
21
|
+
filter = Expect::Redactor.new([])
|
|
22
|
+
assert_equal "\xff\0raw".b, filter.append("\xff\0raw".b)
|
|
23
|
+
assert_empty filter.finish
|
|
24
|
+
assert_equal "plain", Expect::Redactor.redact("plain", [])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_one_shot_redaction_does_not_treat_an_ordinary_suffix_as_a_secret_prefix
|
|
28
|
+
assert_equal "pass", Expect::Redactor.redact("pass", ["password"])
|
|
29
|
+
assert_equal "[REDACTED]!", Expect::Redactor.redact("password!", ["password"], replacement: "[REDACTED]")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_stream_finish_masks_partial_secrets_by_default
|
|
33
|
+
filter = Expect::Redactor.new(["password"])
|
|
34
|
+
assert_equal "prefix [FILTERED]", filter.append("prefix pass") + filter.finish
|
|
35
|
+
assert_empty filter.finish
|
|
36
|
+
assert_equal "[FILTERED]!", filter.append("password!") + filter.finish
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_exact_finish_is_explicit_and_validated
|
|
40
|
+
filter = Expect::Redactor.new(["password"])
|
|
41
|
+
filter.append("pass")
|
|
42
|
+
assert_raises(ArgumentError) { filter.finish(partial: nil) }
|
|
43
|
+
assert_equal "pass", filter.finish(partial: false)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_custom_marker_and_overlapping_patterns_at_every_boundary
|
|
47
|
+
patterns = ["alpha[REDACTED]omega", "[REDACTED]", "REDACTED", "[", "abc", "cde"]
|
|
48
|
+
input = "alpha[REDACTED]omega | [REDACTED] | abcde!"
|
|
49
|
+
expected = "[REDACTED] | [REDACTED] | [REDACTED]!"
|
|
50
|
+
(0..input.bytesize).each do |split|
|
|
51
|
+
filter = Expect::Redactor.new(patterns, replacement: "[REDACTED]")
|
|
52
|
+
actual = filter.append(input.byteslice(0, split)) + filter.append(input.byteslice(split..)) + filter.finish
|
|
53
|
+
assert_equal expected, actual, "split #{split}"
|
|
54
|
+
end
|
|
55
|
+
filter = Expect::Redactor.new(patterns, replacement: "[REDACTED]")
|
|
56
|
+
actual = input.bytes.map { |byte| filter.append(byte.chr) }.join + filter.finish
|
|
57
|
+
assert_equal expected, actual
|
|
58
|
+
assert_equal expected, Expect::Redactor.redact(input, patterns, replacement: "[REDACTED]")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_patterns_and_replacement_are_copied
|
|
62
|
+
secret = +"secret"
|
|
63
|
+
patterns = [secret]
|
|
64
|
+
replacement = +"hidden"
|
|
65
|
+
filter = Expect::Redactor.new(patterns, replacement: replacement)
|
|
66
|
+
secret.replace("public")
|
|
67
|
+
patterns.clear
|
|
68
|
+
replacement.clear
|
|
69
|
+
assert_equal "hidden!", filter.append("secret!") + filter.finish
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_invalid_pattern_update_is_atomic_and_preserves_pending_data
|
|
73
|
+
filter = Expect::Redactor.new(["secret"])
|
|
74
|
+
filter.append("sec")
|
|
75
|
+
[nil, "secret", [""], ["valid", 1]].each do |patterns|
|
|
76
|
+
assert_raises(ArgumentError) { filter.patterns = patterns }
|
|
77
|
+
end
|
|
78
|
+
assert_equal "[FILTERED]!", filter.append("ret!") + filter.finish
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_pattern_update_keeps_hidden_spans_and_matches_new_secret_across_chunks
|
|
82
|
+
filter = Expect::Redactor.new(%w[abc long-pattern])
|
|
83
|
+
filter.append("abcsec")
|
|
84
|
+
filter.patterns = ["secret"]
|
|
85
|
+
assert_equal "[FILTERED]!", filter.append("ret!") + filter.finish
|
|
86
|
+
filter.patterns = []
|
|
87
|
+
assert_equal "secret", filter.append("secret")
|
|
88
|
+
assert_empty filter.finish
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_invalid_input_and_replacement_do_not_mutate_stream
|
|
92
|
+
[nil, "", 3].each do |replacement|
|
|
93
|
+
assert_raises(ArgumentError) { Expect::Redactor.new([], replacement: replacement) }
|
|
94
|
+
end
|
|
95
|
+
filter = Expect::Redactor.new(["secret"])
|
|
96
|
+
filter.append("sec")
|
|
97
|
+
assert_raises(ArgumentError) { filter.append(nil) }
|
|
98
|
+
assert_equal "[FILTERED]!", filter.append("ret!") + filter.finish
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_inspect_does_not_expose_registered_or_pending_bytes
|
|
102
|
+
filter = Expect::Redactor.new(["private-pattern"])
|
|
103
|
+
filter.append("raw-pending")
|
|
104
|
+
assert_equal "#<Expect::Redactor>", filter.inspect
|
|
105
|
+
end
|
|
106
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: expect-pty
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- expect-pty contributors
|
|
@@ -169,6 +169,7 @@ files:
|
|
|
169
169
|
- test/multi_session_test.rb
|
|
170
170
|
- test/pattern_offset_test.rb
|
|
171
171
|
- test/process_test.rb
|
|
172
|
+
- test/redactor_test.rb
|
|
172
173
|
- test/relay_recovery_test.rb
|
|
173
174
|
- test/release_test.rb
|
|
174
175
|
- test/ruby_api_test.rb
|