package ucca import ( "testing" ) // ============================================================================= // License Policy Engine Tests // ============================================================================= func TestNewLicensePolicyEngine(t *testing.T) { engine := NewLicensePolicyEngine() if engine == nil { t.Fatal("Expected non-nil engine") } } // ============================================================================= // Basic Evaluation Tests // ============================================================================= func TestLicensePolicyEngine_NoLicensedContent(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: false, } result := engine.Evaluate(facts) if result.EffectiveMode != "UNRESTRICTED" { t.Errorf("Expected UNRESTRICTED mode for no licensed content, got %s", result.EffectiveMode) } if !result.Allowed { t.Error("Expected allowed=true for no licensed content") } if !result.OutputRestrictions.AllowQuotes { t.Error("Expected AllowQuotes=true for no licensed content") } if !result.OutputRestrictions.AllowCopy { t.Error("Expected AllowCopy=true for no licensed content") } if !result.OutputRestrictions.AllowExport { t.Error("Expected AllowExport=true for no licensed content") } } // ============================================================================= // Operation Mode Tests // ============================================================================= func TestLicensePolicyEngine_LinkOnlyMode(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "VDI", LicenseType: "SINGLE_WORKSTATION", AIUsePermitted: "UNKNOWN", OperationMode: "LINK_ONLY", } result := engine.Evaluate(facts) if result.EffectiveMode != "LINK_ONLY" { t.Errorf("Expected LINK_ONLY mode, got %s", result.EffectiveMode) } if !result.Allowed { t.Error("Expected allowed=true for LINK_ONLY mode") } if result.RiskScore != 0 { t.Errorf("Expected risk score 0 for LINK_ONLY, got %d", result.RiskScore) } if result.OutputRestrictions.AllowQuotes { t.Error("Expected AllowQuotes=false for LINK_ONLY") } if result.OutputRestrictions.AllowCopy { t.Error("Expected AllowCopy=false for LINK_ONLY") } } func TestLicensePolicyEngine_NotesOnlyMode(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "ISO", LicenseType: "NETWORK_INTRANET", AIUsePermitted: "NO", OperationMode: "NOTES_ONLY", } result := engine.Evaluate(facts) if result.EffectiveMode != "NOTES_ONLY" { t.Errorf("Expected NOTES_ONLY mode, got %s", result.EffectiveMode) } if !result.Allowed { t.Error("Expected allowed=true for NOTES_ONLY mode") } if result.RiskScore != 10 { t.Errorf("Expected risk score 10 for NOTES_ONLY, got %d", result.RiskScore) } // Notes can be copied (they are customer's own) if !result.OutputRestrictions.AllowCopy { t.Error("Expected AllowCopy=true for NOTES_ONLY") } } func TestLicensePolicyEngine_NotesOnlyMode_UnknownLicense(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "VDE", LicenseType: "UNKNOWN", AIUsePermitted: "UNKNOWN", OperationMode: "NOTES_ONLY", } result := engine.Evaluate(facts) // Should have escalation level E2 if result.EscalationLevel != "E2" { t.Errorf("Expected escalation level E2 for unknown license, got %s", result.EscalationLevel) } // Should have GAP_LICENSE_UNKNOWN gap hasGap := false for _, gap := range result.Gaps { if gap.ID == "GAP_LICENSE_UNKNOWN" { hasGap = true break } } if !hasGap { t.Error("Expected GAP_LICENSE_UNKNOWN gap for unknown license") } } func TestLicensePolicyEngine_ExcerptOnlyMode_WithAIPermission(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "ISO", LicenseType: "NETWORK_INTRANET", AIUsePermitted: "YES", OperationMode: "EXCERPT_ONLY", } result := engine.Evaluate(facts) if result.EffectiveMode != "EXCERPT_ONLY" { t.Errorf("Expected EXCERPT_ONLY mode, got %s", result.EffectiveMode) } if !result.Allowed { t.Error("Expected allowed=true for EXCERPT_ONLY with AI permission") } // Short quotes should be allowed if !result.OutputRestrictions.AllowQuotes { t.Error("Expected AllowQuotes=true for EXCERPT_ONLY with AI permission") } // But limited to 150 chars if result.OutputRestrictions.MaxQuoteLength != 150 { t.Errorf("Expected MaxQuoteLength=150, got %d", result.OutputRestrictions.MaxQuoteLength) } } func TestLicensePolicyEngine_ExcerptOnlyMode_WithoutAIPermission(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "ISO", LicenseType: "NETWORK_INTRANET", AIUsePermitted: "NO", OperationMode: "EXCERPT_ONLY", } result := engine.Evaluate(facts) // Should be downgraded to NOTES_ONLY if result.EffectiveMode != "NOTES_ONLY" { t.Errorf("Expected NOTES_ONLY mode (downgraded), got %s", result.EffectiveMode) } // Should have GAP_AI_USE_NOT_PERMITTED hasGap := false for _, gap := range result.Gaps { if gap.ID == "GAP_AI_USE_NOT_PERMITTED" { hasGap = true break } } if !hasGap { t.Error("Expected GAP_AI_USE_NOT_PERMITTED gap") } } func TestLicensePolicyEngine_FulltextRAGMode_Allowed(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "VDI", LicenseType: "AI_LICENSE", AIUsePermitted: "YES", ProofUploaded: true, OperationMode: "FULLTEXT_RAG", } result := engine.Evaluate(facts) if result.EffectiveMode != "FULLTEXT_RAG" { t.Errorf("Expected FULLTEXT_RAG mode, got %s", result.EffectiveMode) } if !result.Allowed { t.Error("Expected allowed=true for FULLTEXT_RAG with proof") } if result.StopLine != nil { t.Error("Expected no stop line for allowed FULLTEXT_RAG") } // Quotes allowed but limited if !result.OutputRestrictions.AllowQuotes { t.Error("Expected AllowQuotes=true for FULLTEXT_RAG") } if result.OutputRestrictions.MaxQuoteLength != 500 { t.Errorf("Expected MaxQuoteLength=500, got %d", result.OutputRestrictions.MaxQuoteLength) } } func TestLicensePolicyEngine_FulltextRAGMode_Blocked_NoProof(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "VDI", LicenseType: "NETWORK_INTRANET", AIUsePermitted: "YES", ProofUploaded: false, // No proof! OperationMode: "FULLTEXT_RAG", } result := engine.Evaluate(facts) // Should be blocked and downgraded to LINK_ONLY if result.Allowed { t.Error("Expected allowed=false for FULLTEXT_RAG without proof") } if result.EffectiveMode != "LINK_ONLY" { t.Errorf("Expected LINK_ONLY mode (downgraded), got %s", result.EffectiveMode) } // Should have stop line if result.StopLine == nil { t.Fatal("Expected stop line for blocked FULLTEXT_RAG") } if result.StopLine.ID != "STOP_FULLTEXT_WITHOUT_PROOF" { t.Errorf("Expected stop line STOP_FULLTEXT_WITHOUT_PROOF, got %s", result.StopLine.ID) } // Should have GAP_FULLTEXT_WITHOUT_PROOF hasGap := false for _, gap := range result.Gaps { if gap.ID == "GAP_FULLTEXT_WITHOUT_PROOF" { hasGap = true if gap.Severity != "BLOCK" { t.Error("Expected gap severity BLOCK") } break } } if !hasGap { t.Error("Expected GAP_FULLTEXT_WITHOUT_PROOF gap") } if result.EscalationLevel != "E3" { t.Errorf("Expected escalation level E3, got %s", result.EscalationLevel) } } func TestLicensePolicyEngine_TrainingMode_Blocked(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "DIN_MEDIA", LicenseType: "NETWORK_INTRANET", AIUsePermitted: "NO", ProofUploaded: false, OperationMode: "TRAINING", } result := engine.Evaluate(facts) if result.Allowed { t.Error("Expected allowed=false for TRAINING without AI license") } if result.EffectiveMode != "LINK_ONLY" { t.Errorf("Expected LINK_ONLY mode (downgraded), got %s", result.EffectiveMode) } // Should have stop line if result.StopLine == nil { t.Fatal("Expected stop line for blocked TRAINING") } if result.EscalationLevel != "E3" { t.Errorf("Expected escalation level E3, got %s", result.EscalationLevel) } } func TestLicensePolicyEngine_TrainingMode_Allowed(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "VDI", LicenseType: "AI_LICENSE", AIUsePermitted: "YES", ProofUploaded: true, OperationMode: "TRAINING", } result := engine.Evaluate(facts) if result.EffectiveMode != "TRAINING" { t.Errorf("Expected TRAINING mode, got %s", result.EffectiveMode) } if !result.Allowed { t.Error("Expected allowed=true for TRAINING with AI_LICENSE") } // Still requires E3 review if result.EscalationLevel != "E3" { t.Errorf("Expected escalation level E3 even for allowed training, got %s", result.EscalationLevel) } } // ============================================================================= // Publisher-Specific Tests (DIN Media) // ============================================================================= func TestLicensePolicyEngine_DINMedia_FulltextBlocked(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "DIN_MEDIA", LicenseType: "SINGLE_WORKSTATION", AIUsePermitted: "NO", ProofUploaded: false, OperationMode: "FULLTEXT_RAG", } result := engine.Evaluate(facts) if result.Allowed { t.Error("Expected allowed=false for DIN_MEDIA FULLTEXT_RAG without AI permission") } // Should have DIN-specific stop line if result.StopLine == nil { t.Fatal("Expected stop line for DIN_MEDIA") } if result.StopLine.ID != "STOP_DIN_FULLTEXT_AI_NOT_ALLOWED" { t.Errorf("Expected stop line STOP_DIN_FULLTEXT_AI_NOT_ALLOWED, got %s", result.StopLine.ID) } // Should have CTRL-NO-CRAWLING-DIN control hasControl := false for _, ctrl := range result.RequiredControls { if ctrl.ID == "CTRL-NO-CRAWLING-DIN" { hasControl = true break } } if !hasControl { t.Error("Expected CTRL-NO-CRAWLING-DIN control for DIN_MEDIA") } } func TestLicensePolicyEngine_DINMedia_LinkOnlyAllowed(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "DIN_MEDIA", LicenseType: "SINGLE_WORKSTATION", AIUsePermitted: "NO", OperationMode: "LINK_ONLY", } result := engine.Evaluate(facts) if !result.Allowed { t.Error("Expected allowed=true for DIN_MEDIA LINK_ONLY") } if result.EffectiveMode != "LINK_ONLY" { t.Errorf("Expected LINK_ONLY mode, got %s", result.EffectiveMode) } // Should have no stop line for LINK_ONLY if result.StopLine != nil { t.Error("Expected no stop line for DIN_MEDIA LINK_ONLY") } } func TestLicensePolicyEngine_DINMedia_NotesOnlyAllowed(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "DIN_MEDIA", LicenseType: "NETWORK_INTRANET", AIUsePermitted: "NO", OperationMode: "NOTES_ONLY", } result := engine.Evaluate(facts) if !result.Allowed { t.Error("Expected allowed=true for DIN_MEDIA NOTES_ONLY") } if result.EffectiveMode != "NOTES_ONLY" { t.Errorf("Expected NOTES_ONLY mode, got %s", result.EffectiveMode) } } // ============================================================================= // Distribution Scope Tests // ============================================================================= func TestLicensePolicyEngine_DistributionScopeMismatch(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "ISO", LicenseType: "SINGLE_WORKSTATION", AIUsePermitted: "NO", OperationMode: "LINK_ONLY", DistributionScope: "COMPANY_INTERNAL", } result := engine.Evaluate(facts) // Should have GAP_DISTRIBUTION_SCOPE_MISMATCH hasGap := false for _, gap := range result.Gaps { if gap.ID == "GAP_DISTRIBUTION_SCOPE_MISMATCH" { hasGap = true break } } if !hasGap { t.Error("Expected GAP_DISTRIBUTION_SCOPE_MISMATCH for single workstation with company distribution") } if result.EscalationLevel != "E3" { t.Errorf("Expected escalation level E3, got %s", result.EscalationLevel) } } func TestLicensePolicyEngine_NetworkLicenseExternalDistribution(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "VDI", LicenseType: "NETWORK_INTRANET", AIUsePermitted: "YES", ProofUploaded: true, OperationMode: "NOTES_ONLY", DistributionScope: "EXTERNAL_CUSTOMERS", } result := engine.Evaluate(facts) // Should have GAP_DISTRIBUTION_SCOPE_EXTERNAL hasGap := false for _, gap := range result.Gaps { if gap.ID == "GAP_DISTRIBUTION_SCOPE_EXTERNAL" { hasGap = true break } } if !hasGap { t.Error("Expected GAP_DISTRIBUTION_SCOPE_EXTERNAL for network license with external distribution") } } // ============================================================================= // Helper Function Tests // ============================================================================= func TestLicensePolicyEngine_CanIngestFulltext(t *testing.T) { engine := NewLicensePolicyEngine() tests := []struct { name string facts *LicensedContentFacts expected bool }{ { name: "No licensed content", facts: &LicensedContentFacts{ Present: false, }, expected: true, }, { name: "LINK_ONLY mode", facts: &LicensedContentFacts{ Present: true, OperationMode: "LINK_ONLY", }, expected: false, }, { name: "NOTES_ONLY mode", facts: &LicensedContentFacts{ Present: true, OperationMode: "NOTES_ONLY", }, expected: false, }, { name: "FULLTEXT_RAG with proof", facts: &LicensedContentFacts{ Present: true, OperationMode: "FULLTEXT_RAG", AIUsePermitted: "YES", ProofUploaded: true, }, expected: true, }, { name: "FULLTEXT_RAG without proof", facts: &LicensedContentFacts{ Present: true, OperationMode: "FULLTEXT_RAG", AIUsePermitted: "YES", ProofUploaded: false, }, expected: false, }, { name: "TRAINING with AI_LICENSE", facts: &LicensedContentFacts{ Present: true, OperationMode: "TRAINING", AIUsePermitted: "YES", ProofUploaded: true, LicenseType: "AI_LICENSE", }, expected: true, }, { name: "TRAINING without AI_LICENSE", facts: &LicensedContentFacts{ Present: true, OperationMode: "TRAINING", AIUsePermitted: "YES", ProofUploaded: true, LicenseType: "NETWORK_INTRANET", }, expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := engine.CanIngestFulltext(tt.facts) if result != tt.expected { t.Errorf("Expected %v, got %v", tt.expected, result) } }) } } func TestLicensePolicyEngine_CanIngestNotes(t *testing.T) { engine := NewLicensePolicyEngine() tests := []struct { name string facts *LicensedContentFacts expected bool }{ { name: "No licensed content", facts: &LicensedContentFacts{ Present: false, }, expected: true, }, { name: "LINK_ONLY mode", facts: &LicensedContentFacts{ Present: true, OperationMode: "LINK_ONLY", }, expected: false, }, { name: "NOTES_ONLY mode", facts: &LicensedContentFacts{ Present: true, OperationMode: "NOTES_ONLY", }, expected: true, }, { name: "EXCERPT_ONLY mode", facts: &LicensedContentFacts{ Present: true, OperationMode: "EXCERPT_ONLY", }, expected: true, }, { name: "FULLTEXT_RAG mode", facts: &LicensedContentFacts{ Present: true, OperationMode: "FULLTEXT_RAG", }, expected: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := engine.CanIngestNotes(tt.facts) if result != tt.expected { t.Errorf("Expected %v, got %v", tt.expected, result) } }) } } func TestLicensePolicyEngine_GetEffectiveMode(t *testing.T) { engine := NewLicensePolicyEngine() // Test downgrade scenario facts := &LicensedContentFacts{ Present: true, Publisher: "DIN_MEDIA", LicenseType: "SINGLE_WORKSTATION", AIUsePermitted: "NO", OperationMode: "FULLTEXT_RAG", } effectiveMode := engine.GetEffectiveMode(facts) if effectiveMode != "LINK_ONLY" { t.Errorf("Expected effective mode LINK_ONLY (downgraded), got %s", effectiveMode) } } func TestLicensePolicyEngine_DecideIngest(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "VDI", LicenseType: "AI_LICENSE", AIUsePermitted: "YES", ProofUploaded: true, OperationMode: "FULLTEXT_RAG", } decision := engine.DecideIngest(facts) if !decision.AllowMetadata { t.Error("Expected AllowMetadata=true") } if !decision.AllowNotes { t.Error("Expected AllowNotes=true") } if !decision.AllowFulltext { t.Error("Expected AllowFulltext=true for FULLTEXT_RAG with proof") } if decision.EffectiveMode != "FULLTEXT_RAG" { t.Errorf("Expected EffectiveMode=FULLTEXT_RAG, got %s", decision.EffectiveMode) } } // ============================================================================= // Audit Entry Tests // ============================================================================= func TestLicensePolicyEngine_FormatAuditEntry(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "DIN_MEDIA", LicenseType: "SINGLE_WORKSTATION", AIUsePermitted: "NO", OperationMode: "FULLTEXT_RAG", } result := engine.Evaluate(facts) entry := engine.FormatAuditEntry("tenant-123", "doc-456", facts, result) if entry.TenantID != "tenant-123" { t.Errorf("Expected TenantID=tenant-123, got %s", entry.TenantID) } if entry.DocumentID != "doc-456" { t.Errorf("Expected DocumentID=doc-456, got %s", entry.DocumentID) } if entry.Decision != "DENY" { t.Errorf("Expected Decision=DENY, got %s", entry.Decision) } if entry.StopLineID == "" { t.Error("Expected StopLineID to be set for denied request") } } func TestLicensePolicyEngine_FormatAuditEntry_Downgrade(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "ISO", LicenseType: "NETWORK_INTRANET", AIUsePermitted: "NO", OperationMode: "EXCERPT_ONLY", } result := engine.Evaluate(facts) entry := engine.FormatAuditEntry("tenant-123", "doc-456", facts, result) if entry.Decision != "DOWNGRADE" { t.Errorf("Expected Decision=DOWNGRADE, got %s", entry.Decision) } if entry.EffectiveMode != "NOTES_ONLY" { t.Errorf("Expected EffectiveMode=NOTES_ONLY, got %s", entry.EffectiveMode) } } // ============================================================================= // Human Readable Summary Tests // ============================================================================= func TestLicensePolicyEngine_FormatHumanReadableSummary(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "DIN_MEDIA", LicenseType: "SINGLE_WORKSTATION", AIUsePermitted: "NO", OperationMode: "FULLTEXT_RAG", } result := engine.Evaluate(facts) summary := engine.FormatHumanReadableSummary(result) // Should contain key elements if !stringContains(summary, "BLOCKIERT") { t.Error("Summary should contain 'BLOCKIERT'") } if !stringContains(summary, "LINK_ONLY") { t.Error("Summary should contain 'LINK_ONLY'") } if !stringContains(summary, "STOP-LINE") { t.Error("Summary should contain '!!! STOP-LINE !!!'") } if !stringContains(summary, "DIN Media") { t.Error("Summary should mention DIN Media") } } // ============================================================================= // Determinism Tests // ============================================================================= func TestLicensePolicyEngine_Determinism(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "DIN_MEDIA", LicenseType: "NETWORK_INTRANET", AIUsePermitted: "NO", ProofUploaded: false, OperationMode: "FULLTEXT_RAG", DistributionScope: "COMPANY_INTERNAL", } // Run evaluation 10 times and ensure identical results firstResult := engine.Evaluate(facts) for i := 0; i < 10; i++ { result := engine.Evaluate(facts) if result.Allowed != firstResult.Allowed { t.Errorf("Run %d: Allowed mismatch: %v vs %v", i, result.Allowed, firstResult.Allowed) } if result.EffectiveMode != firstResult.EffectiveMode { t.Errorf("Run %d: EffectiveMode mismatch: %s vs %s", i, result.EffectiveMode, firstResult.EffectiveMode) } if result.RiskScore != firstResult.RiskScore { t.Errorf("Run %d: RiskScore mismatch: %d vs %d", i, result.RiskScore, firstResult.RiskScore) } if len(result.Gaps) != len(firstResult.Gaps) { t.Errorf("Run %d: Gaps count mismatch: %d vs %d", i, len(result.Gaps), len(firstResult.Gaps)) } } } // ============================================================================= // Edge Case Tests // ============================================================================= func TestLicensePolicyEngine_UnknownOperationMode(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{ Present: true, Publisher: "ISO", OperationMode: "INVALID_MODE", } result := engine.Evaluate(facts) // Should default to LINK_ONLY if result.EffectiveMode != "LINK_ONLY" { t.Errorf("Expected default to LINK_ONLY for unknown mode, got %s", result.EffectiveMode) } if !result.Allowed { t.Error("Expected allowed=true for fallback to LINK_ONLY") } } func TestLicensePolicyEngine_EmptyFacts(t *testing.T) { engine := NewLicensePolicyEngine() facts := &LicensedContentFacts{} result := engine.Evaluate(facts) // Empty facts = no licensed content if result.EffectiveMode != "UNRESTRICTED" { t.Errorf("Expected UNRESTRICTED for empty facts, got %s", result.EffectiveMode) } } // ============================================================================= // Risk Score Tests // ============================================================================= func TestLicensePolicyEngine_RiskScores(t *testing.T) { engine := NewLicensePolicyEngine() tests := []struct { name string mode string expectedMin int expectedMax int }{ {"LINK_ONLY", "LINK_ONLY", 0, 0}, {"NOTES_ONLY", "NOTES_ONLY", 10, 30}, {"EXCERPT_ONLY", "EXCERPT_ONLY", 30, 50}, {"FULLTEXT_RAG", "FULLTEXT_RAG", 60, 90}, {"TRAINING", "TRAINING", 80, 100}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { facts := &LicensedContentFacts{ Present: true, Publisher: "VDI", LicenseType: "AI_LICENSE", AIUsePermitted: "YES", ProofUploaded: true, OperationMode: tt.mode, } result := engine.Evaluate(facts) if result.RiskScore < tt.expectedMin || result.RiskScore > tt.expectedMax { t.Errorf("Expected risk score in range [%d, %d], got %d", tt.expectedMin, tt.expectedMax, result.RiskScore) } }) } } // Helper function func stringContains(s, substr string) bool { return len(s) >= len(substr) && (s == substr || len(s) > 0 && stringContainsHelper(s, substr)) } func stringContainsHelper(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }