package aceboot::orm.tests
import aceboot::orm.common.*
import aceboot::orm.sqlite.*
import std.collection.*
import std.unittest.*
import std.unittest.testmacro.*
import aceboot::orm.macros.*
// ===== F3:嵌套点号关系 findOne(id, ["author.profile"]) 测试 =====
// 实体链:Post --author(ManyToOne)--> User --profile(OneToOne owner)--> Profile;
// Post 另有 comments(OneToMany)集合,验证混合路径。
@Entity["nr_profiles"]
public class _NrProfile {
@Id[]
public var id: Int64 = 0
@Column[]
public var bio: String = ""
}
@Entity["nr_users"]
public class _NrUser {
@Id[]
public var id: Int64 = 0
@Column[]
public var name: String = ""
// owner 侧一对一:本表持 FK profileId 指向 profile.id。
@OneToOne[_NrProfile, "nr_profiles", "profileId"]
public var profile: ?_NrProfile = None
}
@Entity["nr_comments"]
public class _NrComment {
@Id[]
public var id: Int64 = 0
@Column[]
public var text: String = ""
@Column["postId"]
public var postId: Int64 = 0
}
@Entity["nr_posts"]
public class _NrPost {
@Id[]
public var id: Int64 = 0
@Column[]
public var title: String = ""
// to-one:本表持 FK authorId 指向 user.id。
@ManyToOne[_NrUser, "nr_users", fk = "authorId"]
public var author: ?_NrUser = None
// 集合:comments 的 postId 反向指回本帖。
@OneToMany[_NrComment, "postId", "nr_comments"]
public var comments: Array<_NrComment> = Array<_NrComment>()
}
func _nrSetup(): (DataSource, Repository<_NrUser>, Repository<_NrProfile>, Repository<_NrPost>, Repository<_NrComment>) {
let ds = DataSource(SqliteDriver(":memory:"))
let userRepo = Repository<_NrUser>(ds, _NrUserMapper())
let profileRepo = Repository<_NrProfile>(ds, _NrProfileMapper())
let postRepo = Repository<_NrPost>(ds, _NrPostMapper())
let commentRepo = Repository<_NrComment>(ds, _NrCommentMapper())
profileRepo.createTable()
userRepo.createTable()
postRepo.createTable()
commentRepo.createTable()
return (ds, userRepo, profileRepo, postRepo, commentRepo)
}
// 造一条 profile→user(持 profileId)→post(持 authorId)的到一链,返回各 id。
func _nrSeedChain(userRepo: Repository<_NrUser>, profileRepo: Repository<_NrProfile>, postRepo: Repository<_NrPost>): (
Int64, Int64, Int64) {
let prof = _NrProfile()
prof.bio = "loaded-bio"
let profId = profileRepo.insert(prof)
let u = _NrUser()
u.name = "Alice"
u.profile = Some(prof) // owner 持 FK profileId
let uid = userRepo.insert(u)
let post = _NrPost()
post.title = "Hello"
post.author = Some(u) // 本表持 FK authorId
let pid = postRepo.insert(post)
return (profId, uid, pid)
}
@Test
func test_nested_two_level_to_one() {
let (ds, userRepo, profileRepo, postRepo, _) = _nrSetup()
let (_, uid, pid) = _nrSeedChain(userRepo, profileRepo, postRepo)
// 两层 to-one 装填:author + author.profile。
let loaded = postRepo.findOne(DbInt(pid), ["author.profile"]).getOrThrow()
@Expect(loaded.title, "Hello")
@Expect(loaded.author.isSome(), true)
let author = loaded.author.getOrThrow()
@Expect(author.name, "Alice")
@Expect(author.id, uid)
// 嵌套段:author.profile 也被装填。
@Expect(author.profile.isSome(), true)
@Expect(author.profile.getOrThrow().bio, "loaded-bio")
ds.driver.close()
}
@Test
func test_nested_single_level_backward_compat() {
let (ds, userRepo, profileRepo, postRepo, _) = _nrSetup()
let (_, _, pid) = _nrSeedChain(userRepo, profileRepo, postRepo)
// 单层(无点号):只装 author,不误装 author.profile。
let loaded = postRepo.findOne(DbInt(pid), ["author"]).getOrThrow()
@Expect(loaded.author.isSome(), true)
@Expect(loaded.author.getOrThrow().name, "Alice")
// 未请求嵌套段 → profile 保持 None。
@Expect(loaded.author.getOrThrow().profile.isNone(), true)
ds.driver.close()
}
@Test
func test_nested_first_segment_dedup() {
let (ds, userRepo, profileRepo, postRepo, _) = _nrSetup()
let (_, _, pid) = _nrSeedChain(userRepo, profileRepo, postRepo)
// 首段分组去重:["author.profile","author"] 只装 author 一次且嵌套仍正确。
let loaded = postRepo.findOne(DbInt(pid), ["author.profile", "author"]).getOrThrow()
@Expect(loaded.author.isSome(), true)
@Expect(loaded.author.getOrThrow().name, "Alice")
@Expect(loaded.author.getOrThrow().profile.isSome(), true)
@Expect(loaded.author.getOrThrow().profile.getOrThrow().bio, "loaded-bio")
ds.driver.close()
}
@Test
func test_nested_missing_intermediate() {
let (ds, _, _, postRepo, _) = _nrSetup()
// 无 author(FK 为空)的帖子:嵌套路径不抛异常、author 保持 None。
let post = _NrPost()
post.title = "Orphan"
let pid = postRepo.insert(post)
let loaded = postRepo.findOne(DbInt(pid), ["author.profile"]).getOrThrow()
@Expect(loaded.title, "Orphan")
@Expect(loaded.author.isNone(), true)
ds.driver.close()
}
@Test
func test_nested_mixed_path_with_collection() {
let (ds, userRepo, profileRepo, postRepo, commentRepo) = _nrSetup()
let (_, _, pid) = _nrSeedChain(userRepo, profileRepo, postRepo)
for (t in ["c1", "c2"]) {
let c = _NrComment()
c.text = t
c.postId = pid
commentRepo.insert(c)
}
// 混合路径:to-one 链(author.profile)+ 集合叶子(comments)同时装填。
let loaded = postRepo.findOne(DbInt(pid), ["author.profile", "comments"]).getOrThrow()
@Expect(loaded.author.isSome(), true)
@Expect(loaded.author.getOrThrow().profile.getOrThrow().bio, "loaded-bio")
@Expect(loaded.comments.size, 2)
ds.driver.close()
}