Line data Source code
1 : #include "backend/engine/semantic/stubs/keys.h"
2 :
3 : #include <string>
4 : #include <vector>
5 :
6 : #include "absl/status/status.h"
7 : #include "absl/status/statusor.h"
8 : #include "backend/engine/semantic/error.h"
9 : #include "backend/engine/semantic/value.h"
10 : #include "gmock/gmock.h"
11 : #include "googlesql/public/type.h"
12 : #include "googlesql/public/type.pb.h"
13 : #include "googlesql/public/value.h"
14 : #include "gtest/gtest.h"
15 :
16 : namespace bigquery_emulator {
17 : namespace backend {
18 : namespace engine {
19 : namespace semantic {
20 : namespace stubs {
21 : namespace {
22 :
23 : using ::testing::HasSubstr;
24 :
25 1 : TEST(KeysNewKeysetTest, ReturnsSentinelBytesForStringArg) {
26 : // The contract pins the sentinel byte-for-byte so client-library
27 : // probes can assert on `KEYS.NEW_KEYSET(...)`'s exact return
28 : // (e.g. checking the leading `bigquery-emulator:keyset:v1:`
29 : // marker before round-tripping through any downstream consumer).
30 : // The trailing segment echoes the requested key type so the
31 : // sentinel still carries the salient input parameter -- BigQuery
32 : // documents that the returned BYTES depend on the key type, and
33 : // the placeholder preserves that observable difference.
34 1 : Value aead_key = Value::String("AEAD_AES_GCM_256");
35 1 : auto r = KeysNewKeyset({aead_key});
36 2 : ASSERT_TRUE(r.ok()) << r.status();
37 1 : EXPECT_EQ(r->type_kind(), ::googlesql::TYPE_BYTES);
38 1 : EXPECT_EQ(r->bytes_value(), "bigquery-emulator:keyset:v1:AEAD_AES_GCM_256");
39 :
40 1 : Value deterministic_key =
41 1 : Value::String("DETERMINISTIC_AEAD_AES_SIV_CMAC_256");
42 1 : auto other = KeysNewKeyset({deterministic_key});
43 2 : ASSERT_TRUE(other.ok()) << other.status();
44 1 : EXPECT_EQ(other->bytes_value(),
45 1 : "bigquery-emulator:keyset:v1:DETERMINISTIC_AEAD_AES_SIV_CMAC_256");
46 : // The two BigQuery-distinct inputs surface as two distinct BYTES
47 : // outputs. A future regression that hard-codes a single byte
48 : // string for every input would silently approximate the
49 : // BigQuery contract; this assertion catches it.
50 1 : EXPECT_NE(r->bytes_value(), other->bytes_value());
51 1 : }
52 :
53 1 : TEST(KeysNewKeysetTest, NullStringPropagatesToNullBytes) {
54 : // Standard BigQuery scalar contract: NULL input -> NULL output of
55 : // the documented return type. The semantic executor's SAFE-mode
56 : // unwrap relies on the function itself producing a typed NULL
57 : // (NULL BYTES) rather than `Value::Null()` so the surrounding
58 : // projection's column-type tag remains BYTES.
59 1 : Value null_key = Value::NullString();
60 1 : auto r = KeysNewKeyset({null_key});
61 2 : ASSERT_TRUE(r.ok()) << r.status();
62 1 : EXPECT_TRUE(r->is_null());
63 1 : EXPECT_EQ(r->type_kind(), ::googlesql::TYPE_BYTES);
64 1 : }
65 :
66 1 : TEST(KeysNewKeysetTest, RejectsWrongArity) {
67 : // 0 or 2+ arguments is a caller bug. The analyzer should reject
68 : // it before the dispatch even fires; this is defense-in-depth so
69 : // a downstream caller invoking the helper directly gets a clean
70 : // INVALID_ARGUMENT instead of an out-of-bounds access.
71 1 : auto zero = KeysNewKeyset({});
72 1 : EXPECT_EQ(zero.status().code(), absl::StatusCode::kInvalidArgument);
73 1 : EXPECT_THAT(std::string(zero.status().message()),
74 1 : HasSubstr("KEYS.NEW_KEYSET"));
75 :
76 1 : Value key_a = Value::String("a");
77 1 : Value key_b = Value::String("b");
78 1 : auto two = KeysNewKeyset({key_a, key_b});
79 1 : EXPECT_EQ(two.status().code(), absl::StatusCode::kInvalidArgument);
80 1 : }
81 :
82 1 : TEST(KeysNewKeysetTest, RejectsNonStringArg) {
83 : // Wrong type also surfaces INVALID_ARGUMENT. The analyzer
84 : // normally inserts an implicit cast from the literal kind to the
85 : // declared signature kind; if this branch fires the caller has
86 : // bypassed the analyzer (or hit a future overload the stub does
87 : // not yet model) and we surface a clean failure rather than
88 : // emitting a sentinel keyed on an INT64.
89 1 : Value wrong_type = Value::Int64(7);
90 1 : auto r = KeysNewKeyset({wrong_type});
91 1 : EXPECT_EQ(r.status().code(), absl::StatusCode::kInvalidArgument);
92 1 : EXPECT_THAT(std::string(r.status().message()), HasSubstr("STRING"));
93 1 : }
94 :
95 1 : TEST(KeysKeysetLengthTest, ReturnsOneForAnyNonNullBytes) {
96 : // BigQuery's `NEW_KEYSET` always returns a single-key keyset;
97 : // the stub pins `KEYSET_LENGTH` to the matching answer. The
98 : // value is identical for the sentinel BYTES the emulator emits
99 : // and any other BYTES the caller might pass -- the local-stub
100 : // posture is "accept-and-return-shaped-answer", not "validate
101 : // the keyset envelope".
102 1 : Value sentinel = Value::Bytes("bigquery-emulator:keyset:v1:AEAD_AES_GCM_256");
103 1 : auto from_sentinel = KeysKeysetLength({sentinel});
104 2 : ASSERT_TRUE(from_sentinel.ok()) << from_sentinel.status();
105 1 : EXPECT_EQ(from_sentinel->type_kind(), ::googlesql::TYPE_INT64);
106 1 : EXPECT_EQ(from_sentinel->int64_value(), 1);
107 :
108 1 : Value arbitrary = Value::Bytes("\x01\x02\x03");
109 1 : auto from_arbitrary = KeysKeysetLength({arbitrary});
110 2 : ASSERT_TRUE(from_arbitrary.ok()) << from_arbitrary.status();
111 1 : EXPECT_EQ(from_arbitrary->int64_value(), 1);
112 :
113 1 : Value empty = Value::Bytes("");
114 1 : auto from_empty = KeysKeysetLength({empty});
115 2 : ASSERT_TRUE(from_empty.ok()) << from_empty.status();
116 1 : EXPECT_EQ(from_empty->int64_value(), 1);
117 1 : }
118 :
119 1 : TEST(KeysKeysetLengthTest, NullBytesPropagatesToNullInt64) {
120 : // Same NULL-propagation contract as the NEW_KEYSET stub. NULL ->
121 : // NULL INT64 so the projection column type tag stays INT64.
122 1 : Value null_keyset = Value::NullBytes();
123 1 : auto r = KeysKeysetLength({null_keyset});
124 2 : ASSERT_TRUE(r.ok()) << r.status();
125 1 : EXPECT_TRUE(r->is_null());
126 1 : EXPECT_EQ(r->type_kind(), ::googlesql::TYPE_INT64);
127 1 : }
128 :
129 1 : TEST(KeysKeysetLengthTest, RejectsWrongArity) {
130 1 : auto zero = KeysKeysetLength({});
131 1 : EXPECT_EQ(zero.status().code(), absl::StatusCode::kInvalidArgument);
132 1 : EXPECT_THAT(std::string(zero.status().message()),
133 1 : HasSubstr("KEYS.KEYSET_LENGTH"));
134 :
135 1 : Value key_a = Value::Bytes("a");
136 1 : Value key_b = Value::Bytes("b");
137 1 : auto two = KeysKeysetLength({key_a, key_b});
138 1 : EXPECT_EQ(two.status().code(), absl::StatusCode::kInvalidArgument);
139 1 : }
140 :
141 1 : TEST(KeysKeysetLengthTest, RejectsNonBytesArg) {
142 : // Wrong type -> INVALID_ARGUMENT. Same reasoning as
143 : // `RejectsNonStringArg` on the NEW_KEYSET stub.
144 1 : Value not_bytes = Value::String("not-bytes");
145 1 : auto r = KeysKeysetLength({not_bytes});
146 1 : EXPECT_EQ(r.status().code(), absl::StatusCode::kInvalidArgument);
147 1 : EXPECT_THAT(std::string(r.status().message()), HasSubstr("BYTES"));
148 1 : }
149 :
150 : } // namespace
151 : } // namespace stubs
152 : } // namespace semantic
153 : } // namespace engine
154 : } // namespace backend
155 : } // namespace bigquery_emulator
|