feat(domain): NewSecret and CredentialVersion structs
CredentialVersion adds a revoked? predicate so callers don't need to nil-check revoked_at directly. Both structs are immutable value types.
This commit is contained in:
parent
fd77a4c323
commit
d91477cfa1
|
|
@ -0,0 +1,51 @@
|
|||
# ===================
|
||||
# ©AngelaMos | 2026
|
||||
# credential_version_spec.cr
|
||||
# ===================
|
||||
|
||||
require "../../spec_helper"
|
||||
require "../../../src/cre/domain/credential_version"
|
||||
|
||||
describe CRE::Domain::CredentialVersion do
|
||||
it "constructs with all fields" do
|
||||
v = CRE::Domain::CredentialVersion.new(
|
||||
id: UUID.random,
|
||||
credential_id: UUID.random,
|
||||
ciphertext: "x".to_slice,
|
||||
dek_wrapped: "y".to_slice,
|
||||
kek_version: 1,
|
||||
algorithm_id: 1_i16,
|
||||
metadata: {} of String => String,
|
||||
)
|
||||
v.kek_version.should eq 1
|
||||
v.algorithm_id.should eq 1_i16
|
||||
v.revoked_at.should be_nil
|
||||
end
|
||||
|
||||
it "reports revocation" do
|
||||
revoked_at = Time.utc - 1.hour
|
||||
v = CRE::Domain::CredentialVersion.new(
|
||||
id: UUID.random,
|
||||
credential_id: UUID.random,
|
||||
ciphertext: Bytes.new(0),
|
||||
dek_wrapped: Bytes.new(0),
|
||||
kek_version: 1,
|
||||
algorithm_id: 1_i16,
|
||||
revoked_at: revoked_at,
|
||||
)
|
||||
v.revoked?.should be_true
|
||||
v.revoked_at.should eq revoked_at
|
||||
end
|
||||
|
||||
it "is not revoked by default" do
|
||||
v = CRE::Domain::CredentialVersion.new(
|
||||
id: UUID.random,
|
||||
credential_id: UUID.random,
|
||||
ciphertext: Bytes.new(0),
|
||||
dek_wrapped: Bytes.new(0),
|
||||
kek_version: 1,
|
||||
algorithm_id: 1_i16,
|
||||
)
|
||||
v.revoked?.should be_false
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# ===================
|
||||
# ©AngelaMos | 2026
|
||||
# new_secret_spec.cr
|
||||
# ===================
|
||||
|
||||
require "../../spec_helper"
|
||||
require "../../../src/cre/domain/new_secret"
|
||||
|
||||
describe CRE::Domain::NewSecret do
|
||||
it "wraps ciphertext + metadata + timestamp" do
|
||||
s = CRE::Domain::NewSecret.new(
|
||||
ciphertext: "abc".to_slice,
|
||||
metadata: {"version_id" => "v123"},
|
||||
)
|
||||
s.ciphertext.should eq "abc".to_slice
|
||||
s.metadata["version_id"].should eq "v123"
|
||||
s.generated_at.should be_close(Time.utc, 5.seconds)
|
||||
end
|
||||
|
||||
it "defaults metadata to empty hash" do
|
||||
s = CRE::Domain::NewSecret.new(ciphertext: Bytes.new(8))
|
||||
s.metadata.should be_empty
|
||||
end
|
||||
|
||||
it "ciphertext is exposed as Bytes" do
|
||||
s = CRE::Domain::NewSecret.new(ciphertext: Bytes[1, 2, 3])
|
||||
s.ciphertext.size.should eq 3
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# ===================
|
||||
# ©AngelaMos | 2026
|
||||
# credential_version.cr
|
||||
# ===================
|
||||
|
||||
require "uuid"
|
||||
|
||||
module CRE::Domain
|
||||
struct CredentialVersion
|
||||
getter id : UUID
|
||||
getter credential_id : UUID
|
||||
getter ciphertext : Bytes
|
||||
getter dek_wrapped : Bytes
|
||||
getter kek_version : Int32
|
||||
getter algorithm_id : Int16
|
||||
getter metadata : Hash(String, String)
|
||||
getter generated_at : Time
|
||||
getter revoked_at : Time?
|
||||
|
||||
def initialize(
|
||||
@id : UUID,
|
||||
@credential_id : UUID,
|
||||
@ciphertext : Bytes,
|
||||
@dek_wrapped : Bytes,
|
||||
@kek_version : Int32,
|
||||
@algorithm_id : Int16,
|
||||
@metadata : Hash(String, String) = {} of String => String,
|
||||
@generated_at : Time = Time.utc,
|
||||
@revoked_at : Time? = nil,
|
||||
)
|
||||
end
|
||||
|
||||
def revoked? : Bool
|
||||
!@revoked_at.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# ===================
|
||||
# ©AngelaMos | 2026
|
||||
# new_secret.cr
|
||||
# ===================
|
||||
|
||||
module CRE::Domain
|
||||
struct NewSecret
|
||||
getter ciphertext : Bytes
|
||||
getter metadata : Hash(String, String)
|
||||
getter generated_at : Time
|
||||
|
||||
def initialize(
|
||||
@ciphertext : Bytes,
|
||||
@metadata : Hash(String, String) = {} of String => String,
|
||||
@generated_at : Time = Time.utc,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue