1use std::collections::HashMap;
7
8use anyhow::Result;
9
10use crate::core::row::Row;
11
12use super::{LdapDirectory, apply_filter_and_projection};
13
14#[derive(Debug, Clone)]
34pub struct MockLdapClient {
35 users: HashMap<String, Row>,
36 netgroups: HashMap<String, Row>,
37}
38
39impl Default for MockLdapClient {
40 fn default() -> Self {
41 let mut users = HashMap::new();
42 users.insert(
43 "oistes".to_string(),
44 crate::row! {
45 "uid" => "oistes",
46 "cn" => "Øistein Søvik",
47 "uidNumber" => "361000",
48 "gidNumber" => "346297",
49 "homeDirectory" => "/uio/kant/usit-gsd-u1/oistes",
50 "loginShell" => "/local/gnu/bin/bash",
51 "objectClass" => ["uioMembership", "top", "account", "posixAccount"],
52 "eduPersonAffiliation" => ["employee", "member", "staff"],
53 "uioAffiliation" => "ANSATT@373034",
54 "uioPrimaryAffiliation" => "ANSATT@373034",
55 "netgroups" => ["ucore", "usit", "it-uio-azure-users"],
56 "filegroups" => ["oistes", "ucore", "usit"]
57 },
58 );
59
60 let mut netgroups = HashMap::new();
61 netgroups.insert(
62 "ucore".to_string(),
63 crate::row! {
64 "cn" => "ucore",
65 "description" => "Kjernen av Unix-grupp på USIT",
66 "objectClass" => ["top", "nisNetgroup"],
67 "members" => [
68 "andreasd",
69 "arildlj",
70 "kjetilk",
71 "oistes",
72 "trondham",
73 "werner"
74 ]
75 },
76 );
77
78 Self { users, netgroups }
79 }
80}
81
82impl LdapDirectory for MockLdapClient {
83 fn user(
84 &self,
85 uid: &str,
86 filter: Option<&str>,
87 attributes: Option<&[String]>,
88 ) -> Result<Vec<Row>> {
89 let raw_rows = if uid.contains('*') {
90 self.users
91 .iter()
92 .filter(|(key, _)| wildcard_match(uid, key))
93 .map(|(_, row)| row.clone())
94 .collect::<Vec<Row>>()
95 } else {
96 self.users
97 .get(uid)
98 .cloned()
99 .map(|row| vec![row])
100 .unwrap_or_default()
101 };
102
103 Ok(apply_filter_and_projection(raw_rows, filter, attributes))
104 }
105
106 fn netgroup(
107 &self,
108 name: &str,
109 filter: Option<&str>,
110 attributes: Option<&[String]>,
111 ) -> Result<Vec<Row>> {
112 let raw_rows = if name.contains('*') {
113 self.netgroups
114 .iter()
115 .filter(|(key, _)| wildcard_match(name, key))
116 .map(|(_, row)| row.clone())
117 .collect::<Vec<Row>>()
118 } else {
119 self.netgroups
120 .get(name)
121 .cloned()
122 .map(|row| vec![row])
123 .unwrap_or_default()
124 };
125
126 Ok(apply_filter_and_projection(raw_rows, filter, attributes))
127 }
128}
129
130fn wildcard_match(pattern: &str, value: &str) -> bool {
131 let escaped = regex::escape(pattern).replace("\\*", ".*");
132 match regex::Regex::new(&format!("^{escaped}$")) {
133 Ok(re) => re.is_match(value),
134 Err(_) => false,
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::MockLdapClient;
141 use crate::ports::LdapDirectory;
142
143 #[test]
144 fn user_filter_uid_equals_returns_match() {
145 let ldap = MockLdapClient::default();
146 let rows = ldap
147 .user("oistes", Some("uid=oistes"), None)
148 .expect("query should succeed");
149 assert_eq!(rows.len(), 1);
150 }
151
152 #[test]
153 fn wildcard_queries_match_users_and_netgroups() {
154 let ldap = MockLdapClient::default();
155
156 let users = ldap.user("oi*", None, None).expect("query should succeed");
157 assert_eq!(users.len(), 1);
158 assert_eq!(
159 users[0].get("uid").and_then(|value| value.as_str()),
160 Some("oistes")
161 );
162
163 let netgroups = ldap
164 .netgroup("u*", None, Some(&["cn".to_string()]))
165 .expect("query should succeed");
166 assert_eq!(netgroups.len(), 1);
167 assert_eq!(
168 netgroups[0].get("cn").and_then(|value| value.as_str()),
169 Some("ucore")
170 );
171 assert_eq!(netgroups[0].len(), 1);
172 }
173
174 #[test]
175 fn missing_entries_return_empty_results() {
176 let ldap = MockLdapClient::default();
177
178 let users = ldap
179 .user("does-not-exist", Some("uid=does-not-exist"), None)
180 .expect("query should succeed");
181 assert!(users.is_empty());
182
183 let netgroups = ldap
184 .netgroup("nope*", None, None)
185 .expect("query should succeed");
186 assert!(netgroups.is_empty());
187 }
188
189 #[test]
190 fn exact_netgroup_queries_return_single_match() {
191 let ldap = MockLdapClient::default();
192
193 let netgroups = ldap
194 .netgroup("ucore", None, Some(&["cn".to_string()]))
195 .expect("query should succeed");
196
197 assert_eq!(netgroups.len(), 1);
198 assert_eq!(
199 netgroups[0].get("cn").and_then(|value| value.as_str()),
200 Some("ucore")
201 );
202 }
203}